3 min read
Building Multi-Stage YAML Pipelines in Azure DevOps
With remote teams becoming the norm, having robust CI/CD pipelines is more important than ever. Azure DevOps multi-stage YAML pipelines provide a way to define your entire deployment workflow as code. Let me show you how to set one up.
Why YAML Pipelines?
- Version controlled - Pipeline definition lives with your code
- Reviewable - Changes go through pull requests
- Reusable - Create templates for common patterns
- Portable - Easy to copy between projects
Basic Structure
# azure-pipelines.yml
trigger:
branches:
include:
- main
- develop
pool:
vmImage: 'ubuntu-latest'
variables:
buildConfiguration: 'Release'
dotnetVersion: '3.1.x'
stages:
- stage: Build
displayName: 'Build and Test'
jobs:
- job: BuildJob
steps:
- task: UseDotNet@2
displayName: 'Use .NET Core SDK'
inputs:
version: $(dotnetVersion)
- task: DotNetCoreCLI@2
displayName: 'Restore packages'
inputs:
command: 'restore'
projects: '**/*.csproj'
- task: DotNetCoreCLI@2
displayName: 'Build solution'
inputs:
command: 'build'
projects: '**/*.csproj'
arguments: '--configuration $(buildConfiguration)'
- task: DotNetCoreCLI@2
displayName: 'Run tests'
inputs:
command: 'test'
projects: '**/*Tests.csproj'
arguments: '--configuration $(buildConfiguration) --collect:"XPlat Code Coverage"'
- task: PublishBuildArtifacts@1
displayName: 'Publish artifacts'
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'drop'
Adding Deployment Stages
stages:
- stage: Build
# ... build jobs from above
- stage: DeployDev
displayName: 'Deploy to Development'
dependsOn: Build
condition: succeeded()
jobs:
- deployment: DeployWebApp
displayName: 'Deploy Web App'
environment: 'Development'
strategy:
runOnce:
deploy:
steps:
- download: current
artifact: drop
- task: AzureWebApp@1
displayName: 'Deploy to Azure Web App'
inputs:
azureSubscription: 'Azure-Service-Connection'
appType: 'webApp'
appName: 'myapp-dev'
package: '$(Pipeline.Workspace)/drop/**/*.zip'
- stage: DeployProd
displayName: 'Deploy to Production'
dependsOn: DeployDev
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: DeployWebApp
displayName: 'Deploy Web App'
environment: 'Production'
strategy:
runOnce:
deploy:
steps:
- download: current
artifact: drop
- task: AzureWebApp@1
displayName: 'Deploy to Azure Web App'
inputs:
azureSubscription: 'Azure-Service-Connection'
appType: 'webApp'
appName: 'myapp-prod'
package: '$(Pipeline.Workspace)/drop/**/*.zip'
Using Templates
Create reusable templates for common tasks:
# templates/dotnet-build.yml
parameters:
- name: buildConfiguration
default: 'Release'
- name: projects
default: '**/*.csproj'
steps:
- task: DotNetCoreCLI@2
displayName: 'Restore packages'
inputs:
command: 'restore'
projects: ${{ parameters.projects }}
- task: DotNetCoreCLI@2
displayName: 'Build'
inputs:
command: 'build'
projects: ${{ parameters.projects }}
arguments: '--configuration ${{ parameters.buildConfiguration }}'
Use the template:
stages:
- stage: Build
jobs:
- job: BuildJob
steps:
- template: templates/dotnet-build.yml
parameters:
buildConfiguration: 'Release'
Environment Approvals
Configure approvals in Azure DevOps:
- Go to Pipelines > Environments
- Select your environment (e.g., Production)
- Click the three dots > Approvals and checks
- Add approvers
Variable Groups
Store sensitive configuration securely:
variables:
- group: 'Production-Secrets'
- name: buildConfiguration
value: 'Release'
Conditional Execution
- stage: DeployProd
condition: |
and(
succeeded(),
eq(variables['Build.SourceBranch'], 'refs/heads/main'),
ne(variables['Build.Reason'], 'PullRequest')
)
Running Jobs in Parallel
- stage: Test
jobs:
- job: UnitTests
steps:
- script: dotnet test --filter Category=Unit
- job: IntegrationTests
steps:
- script: dotnet test --filter Category=Integration
- job: E2ETests
dependsOn: [] # Run in parallel
steps:
- script: npm run e2e
Multi-stage YAML pipelines bring consistency and maintainability to your deployment processes, essential for distributed teams working remotely.