CI/CD in Azure DevOps
How to structure a continuous delivery pipeline in Azure DevOps with build, tests, quality gate and multi-environment deploy.
A well-designed CI/CD pipeline is the difference between deploying with fear and deploying with confidence. In this post I show the structure I use in .NET projects.
Basic structure
The pipeline is split into stages: build, test, quality gate and deploy. Each stage runs only if the previous one passes.
trigger:
branches:
include:
- main
- develop
pool:
vmImage: ubuntu-latest
variables:
buildConfiguration: Release
projectPath: src/MyApp/MyApp.csproj
stages:
- stage: Build
jobs:
- job: BuildJob
steps:
- task: UseDotNet@2
inputs:
version: 8.x
- task: DotNetCoreCLI@2
displayName: Restore
inputs:
command: restore
projects: $(projectPath)
- task: DotNetCoreCLI@2
displayName: Build
inputs:
command: build
projects: $(projectPath)
arguments: --configuration $(buildConfiguration)
- task: DotNetCoreCLI@2
displayName: Publish
inputs:
command: publish
projects: $(projectPath)
publishWebProjects: true
arguments: --configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)
- task: PublishBuildArtifacts@1
inputs:
pathtoPublish: $(Build.ArtifactStagingDirectory)
artifactName: dropTests and quality
After the build, I run tests with coverage and publish the result:
- stage: Test
dependsOn: Build
jobs:
- job: TestJob
steps:
- task: DotNetCoreCLI@2
displayName: Test
inputs:
command: test
arguments: --configuration $(buildConfiguration) /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura
- task: PublishCodeCoverageResults@2
inputs:
summaryFileLocation: $(Agent.TempDirectory)/**/coverage.cobertura.xmlMulti-environment deploy
For each environment (dev, staging, prod) I use a separate stage with approvals:
- stage: DeployProd
dependsOn: Test
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: DeployProd
environment: production
strategy:
runOnce:
deploy:
steps:
- task: AzureWebApp@1
inputs:
azureSubscription: my-service-connection
appName: myapp-prod
package: $(Pipeline.Workspace)/drop
environment: productionenables manual approvals in Azure DevOps — essential for any deploy that touches production.
Conclusion
The secret of a good pipeline is small, fast and reliable. Every second saved in the build is a second multiplied by every dev on the team. Every well-placed gate is an incident avoided.