branches: [main, develop]``` pull_request:
branches: [main]
jobs: build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --configuration Release --no-restore
- name: Run tests
run: dotnet test --no-build --verbosity normal
### Multi-Environment Workflows
**Conditional Deployments:**
```yaml
name: Deploy to Environments
on:
push:
```yaml
branches:
- main
- develop
jobs: build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: webapp
path: dist/
deploy-dev:
needs: build
if: github.ref == 'refs/heads/develop'
runs-on: ubuntu-latest
environment: development
steps:
- uses: actions/download-artifact@v3
with:
name: webapp
- name: Deploy to Azure Web App
uses: azure/webapps-deploy@v2
with:
app-name: webapp-dev
publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE_DEV }}
package: .
deploy-prod:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment:
name: production
url: https://app.contoso.com
steps:
- uses: actions/download-artifact@v3
with:
name: webapp
- name: Deploy to Azure Web App
uses: azure/webapps-deploy@v2
with:
app-name: webapp-prod
publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE_PROD }}
package: .
### Matrix Builds
**Test Across Multiple Versions:**
```yaml
jobs:
test:
```yaml
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [18.x, 20.x, 22.x]
exclude:
- os: macos-latest
node-version: 18.x
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test
### Reusable Workflows
**`.github/workflows/reusable-build.yml`:**
```yaml
name: Reusable Build Workflow
on:
workflow_call:
```yaml
inputs:
environment:
required: true
type: string
node-version:
required: false
type: string
default: '20'
secrets:
deploy-key:
required: true
jobs: build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- run: npm ci
- run: npm run build:${{ inputs.environment }}
- name: Deploy
env:
DEPLOY_KEY: ${{ secrets.deploy-key }}
run: ./deploy.sh
**Call Reusable Workflow:**
```yaml
jobs:
deploy-dev:
```yaml
uses: ./.github/workflows/reusable-build.yml
with:
environment: dev
node-version: '20'
secrets:
deploy-key: ${{ secrets.DEV_DEPLOY_KEY }}
## Azure DevOps Pipelines

### YAML Pipeline Structure
**`azure-pipelines.yml`:**
```yaml
trigger:
branches:
```yaml
include:
- main
- develop```
paths:
```yaml
include:
- src/*
exclude:
- docs/*
pool: vmImage: 'ubuntu-latest'
variables: buildConfiguration: 'Release' dotnetVersion: '8.0.x'
stages:
- stage: Build
displayName: 'Build Application'
jobs:
- job: BuildJob
displayName: 'Build and Test'
steps:
- task: UseDotNet@2
displayName: 'Install .NET SDK'
inputs:
version: $(dotnetVersion)
- task: DotNetCoreCLI@2
displayName: 'Restore dependencies'
inputs:
command: 'restore'
projects: '**/*.csproj'
- task: DotNetCoreCLI@2
displayName: 'Build solution'
inputs:
command: 'build'
projects: '**/*.csproj'
arguments: '--configuration $(buildConfiguration)'
- task: DotNetCoreCLI@2
displayName: 'Run unit tests'
inputs:
command: 'test'
projects: '**/*Tests.csproj'
arguments: '--configuration $(buildConfiguration) --collect:"XPlat Code Coverage"'
- task: PublishCodeCoverageResults@1
displayName: 'Publish code coverage'
inputs:
codeCoverageTool: 'Cobertura'
summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml'
- task: DotNetCoreCLI@2
displayName: 'Publish application'
inputs:
command: 'publish'
publishWebProjects: true
arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)'
- task: PublishBuildArtifacts@1
displayName: 'Publish artifacts'
inputs:
pathToPublish: '$(Build.ArtifactStagingDirectory)'
artifactName: 'drop'
- stage: Deploy_Dev
displayName: 'Deploy to Dev'
dependsOn: Build
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop'))
jobs:
- deployment: DeployDev
environment: 'development'
strategy:
runOnce:
deploy:
steps:
- task: AzureWebApp@1
displayName: 'Deploy to Azure Web App'
inputs:
azureSubscription: 'Azure-Dev'
appName: 'webapp-dev'
package: '$(Pipeline.Workspace)/drop/**/*.zip'
- stage: Deploy_Prod
displayName: 'Deploy to Production'
dependsOn: Build
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: DeployProd
environment: 'production'
strategy:
runOnce:
preDeploy:
steps:
- script: echo "Running pre-deployment checks"
deploy:
steps:
- task: AzureWebApp@1
displayName: 'Deploy to Azure Web App'
inputs:
azureSubscription: 'Azure-Prod'
appName: 'webapp-prod'
package: '$(Pipeline.Workspace)/drop/**/*.zip'
deploymentMethod: 'zipDeploy'
postDeploy:
steps:
- script: curl -f https://webapp-prod.azurewebsites.net/health || exit 1
displayName: 'Health check'
### Pipeline Templates
**`templates/build-template.yml`:**
```yaml
parameters:
- name: projectPath
```yaml
type: string```
- name: buildConfiguration
```yaml
type: string
default: 'Release'
steps:
- task: UseDotNet@2
inputs:
version: '8.0.x'
- task: DotNetCoreCLI@2
displayName: 'Restore'
inputs:
command: 'restore'
projects: '${{ parameters.projectPath }}'
- task: DotNetCoreCLI@2
displayName: 'Build'
inputs:
command: 'build'
projects: '${{ parameters.projectPath }}'
arguments: '--configuration ${{ parameters.buildConfiguration }}'
**Use Template:**
```yaml
stages:
- stage: Build
```yaml
jobs:
- job: BuildAPI
steps:
- template: templates/build-template.yml
parameters:
projectPath: 'src/API/API.csproj'
buildConfiguration: 'Release'
- job: BuildWorker
steps:
- template: templates/build-template.yml
parameters:
projectPath: 'src/Worker/Worker.csproj'
## Testing Strategies
### Unit Tests in CI
**GitHub Actions:**
```yaml
- name: Run unit tests with coverage
run: |
```text
dotnet test \
--configuration Release \
--no-build \
--collect:"XPlat Code Coverage" \
--results-directory ./coverage
Expected output:
Passed! - Failed: 0, Passed: 24, Skipped: 0, Total: 24, Duration: 1.8 s
- name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with:
files: ./coverage/**/coverage.cobertura.xml
fail_ci_if_error: true
### Integration Tests
**Docker Compose for Dependencies:**
```yaml
- name: Start dependencies
run: docker-compose -f docker-compose.test.yml up -d
- name: Wait for services
run: |
```text
timeout 60 bash -c 'until docker exec postgres pg_isready; do sleep 1; done'
-
name: Run integration tests run: dotnet test IntegrationTests.csproj --filter Category=Integration
-
name: Teardown if: always() run: docker-compose -f docker-compose.test.yml down
### End-to-End Tests
**Playwright E2E Tests:**
```yaml
- name: Install Playwright
run: npx playwright install --with-deps
- name: Run E2E tests
run: npx playwright test
env:
```yaml
BASE_URL: https://webapp-staging.azurewebsites.net
- name: Upload test results if: always() uses: actions/upload-artifact@v3 with:
name: playwright-report
path: playwright-report/
## Security Scanning

### GitHub Advanced Security
```yaml
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
```yaml
languages: csharp, javascript
-
name: Autobuild uses: github/codeql-action/autobuild@v3
-
name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3
### Dependency Scanning
```yaml
- name: Run Snyk security scan
uses: snyk/actions/dotnet@master
env:
```yaml
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}```
with:
```yaml
args: --severity-threshold=high
### Container Image Scanning
```yaml
- name: Build Docker image
run: docker build -t myapp:${{ github.sha }} .
- name: Scan image with Trivy
uses: aquasecurity/trivy-action@master
with:
```yaml
image-ref: myapp:${{ github.sha }}
format: 'sarif'
output: 'trivy-results.sarif'
Expected output:
[+] Building 24.3s (12/12) FINISHED
=> naming to docker.io/library/myapp:latest
- name: Upload results to GitHub Security uses: github/codeql-action/upload-sarif@v3 with:
sarif_file: 'trivy-results.sarif'
## Deployment Patterns
### Blue-Green Deployment
**Azure DevOps:**
```yaml
- task: AzureAppServiceManage@0
displayName: 'Swap deployment slots'
inputs:
```yaml
azureSubscription: 'Azure-Prod'
action: 'Swap Slots'
webAppName: 'webapp-prod'
resourceGroupName: 'rg-prod'
sourceSlot: 'staging'
targetSlot: 'production'
### Canary Deployment
**GitHub Actions with Traffic Splitting:**
```yaml
- name: Deploy canary (10% traffic)
uses: azure/webapps-deploy@v2
with:
```yaml
app-name: webapp-prod
slot-name: canary
package: .
- name: Route 10% traffic to canary run: |
az webapp traffic-routing set \
--resource-group rg-prod \
--name webapp-prod \
--distribution canary=10
-
name: Monitor metrics run: ./scripts/monitor-canary.sh timeout-minutes: 30
-
name: Promote canary or rollback run: |
if [ "$CANARY_SUCCESS" = "true" ]; then
az webapp traffic-routing set --distribution canary=100
else
az webapp traffic-routing clear
fi
## Pipeline Optimization

### Caching Dependencies
**GitHub Actions:**
```yaml
- name: Cache npm dependencies
uses: actions/cache@v3
with:
```yaml
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Cache NuGet packages uses: actions/cache@v3 with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
**Azure DevOps:**
```yaml
- task: Cache@2
inputs:
```yaml
key: 'nuget | "$(Agent.OS)" | **/packages.lock.json'
path: $(NUGET_PACKAGES)```
displayName: 'Cache NuGet packages'
Parallel Jobs
GitHub Actions:
jobs:
test-unit:
```yaml
runs-on: ubuntu-latest
steps: [...]
test-integration:
runs-on: ubuntu-latest
steps: [...]
lint:
runs-on: ubuntu-latest
steps: [...]
security-scan:
runs-on: ubuntu-latest
steps: [...]
## Monitoring & Notifications
### Slack Notifications
**GitHub Actions:**
```yaml
- name: Notify Slack on failure
if: failure()
uses: slackapi/slack-github-action@v1
with:
```yaml
payload: |
{
"text": "Build failed: ${{ github.repository }} - ${{ github.ref }}"
}```
env:
```yaml
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
### Application Insights Integration
**Azure DevOps:**
```yaml
- task: AzureCLI@2
displayName: 'Track deployment'
inputs:
```yaml
azureSubscription: 'Azure-Prod'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az monitor app-insights events custom \
--app webapp-prod-insights \
--event-type Deployment \
--properties '{"version":"$(Build.BuildNumber)","result":"success"}'
## Best Practices
1. **Fail Fast**: Run quick tests (linting, unit tests) before expensive operations
2. **Immutable Artifacts**: Build once, deploy everywhere
3. **Environment Parity**: Keep dev/staging/prod as similar as possible
4. **Secrets Management**: Use GitHub Secrets or Azure Key Vault, never hardcode
5. **Version Control Everything**: Pipelines, configuration, infrastructure
6. **Branch Protection**: Require CI success before merging to main
7. **Deployment Gates**: Manual approvals for production deployments
8. **Rollback Strategy**: Automated rollback on health check failures
## Troubleshooting
**Pipeline Failures:**
```bash
# GitHub Actions debug mode
## Add secret: ACTIONS_STEP_DEBUG = true
## Azure DevOps diagnostic logging
System.Debug: true
Intermittent Test Failures:
- Add retry logic for flaky tests
- Increase timeouts for network calls
- Use test isolation strategies
Architecture Decision and Tradeoffs
When designing development workflow solutions with Developer Tools, consider these key architectural trade-offs:
| Approach | Best For | Tradeoff |
|---|---|---|
| Managed / platform service | Rapid delivery, reduced ops burden | Less customisation, potential vendor lock-in |
| Custom / self-hosted | Full control, advanced tuning | Higher operational overhead and cost |
Recommendation: Start with the managed approach for most workloads and move to custom only when specific requirements demand it.
Validation and Versioning
- Last validated: April 2026
- Validate examples against your tenant, region, and SKU constraints before production rollout.
- Keep module, CLI, and SDK versions pinned in automation pipelines and review quarterly.
Security and Governance Considerations
- Apply least-privilege access using RBAC roles and just-in-time elevation for admin tasks.
- Store secrets in managed secret stores and avoid embedding credentials in scripts or source files.
- Enable audit logging, data protection policies, and periodic access reviews for regulated workloads.
Cost and Performance Notes
- Define budgets and alerts, then monitor usage and cost trends continuously after go-live.
- Baseline performance with synthetic and real-user checks before and after major changes.
- Scale resources with measured thresholds and revisit sizing after usage pattern changes.
Official Microsoft References
- https://learn.microsoft.com/visualstudio/
- https://learn.microsoft.com/azure/devops/
- https://learn.microsoft.com/github/
Public Examples from Official Sources
- These examples are sourced from official public Microsoft documentation and sample repositories.
- Documentation examples: https://learn.microsoft.com/visualstudio/
- Sample repositories: https://github.com/microsoft/vscode-extension-samples
- Prefer adapting these examples to your tenant, subscriptions, and governance requirements before production use.
Key Takeaways
- GitHub Actions excels at simplicity and GitHub integration
- Azure DevOps provides enterprise features (approvals, gates, extensions)
- Automate security scanning in every pipeline run
- Cache dependencies to reduce build times
- Implement deployment strategies (blue-green, canary) for zero-downtime releases
Next Steps
- Explore GitHub Environments for deployment protection rules
- Implement Infrastructure as Code with Bicep or Terraform in pipelines
- Add smoke tests post-deployment for immediate validation
- Configure branch policies to enforce CI success
Additional Resources
- GitHub Actions Documentation
- Azure Pipelines Documentation
- YAML Pipeline Schema Reference
- GitHub Actions Marketplace
Automate everything, deploy with confidence.
Discussion