name: azure-devops-pipelines description: Build Azure DevOps YAML pipelines for CI/CD, manage releases, artifacts, and deployment environments. Create build and release pipelines, configure triggers, templates, and integrate with Azure services. Use when automating builds, tests, and deployments in Azure DevOps. (project)
Azure DevOps Pipelines
Expert guidance for Azure DevOps CI/CD pipeline configuration.
When to Use This Skill
- Creating YAML-based build pipelines
- Setting up multi-stage deployments
- Configuring pipeline triggers and schedules
- Creating reusable pipeline templates
- Managing artifacts and releases
- Integrating with Azure services
- Setting up deployment environments
Basic Pipeline Structure
# azure-pipelines.yml
trigger:
branches:
include:
- main
- develop
paths:
exclude:
- docs/*
- README.md
pr:
branches:
include:
- main
pool:
vmImage: 'ubuntu-latest'
variables:
- name: buildConfiguration
value: 'Release'
- group: my-variable-group
stages:
- stage: Build
displayName: 'Build Stage'
jobs:
- job: BuildJob
steps:
- task: DotNetCoreCLI@2
inputs:
command: 'build'
projects: '**/*.csproj'
arguments: '--configuration $(buildConfiguration)'
- stage: Test
displayName: 'Test Stage'
dependsOn: Build
jobs:
- job: TestJob
steps:
- task: DotNetCoreCLI@2
inputs:
command: 'test'
projects: '**/*Tests.csproj'
- stage: Deploy
displayName: 'Deploy Stage'
dependsOn: Test
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: DeployJob
environment: 'production'
strategy:
runOnce:
deploy:
steps:
- script: echo 'Deploying...'
Triggers
# Branch triggers
trigger:
branches:
include:
- main
- release/*
exclude:
- feature/*
# Path filters
trigger:
paths:
include:
- src/*
exclude:
- src/tests/*
# Tag triggers
trigger:
tags:
include:
- v*
# Scheduled triggers
schedules:
- cron: '0 0 * * *'
displayName: 'Nightly build'
branches:
include:
- main
always: true
# PR triggers
pr:
branches:
include:
- main
paths:
include:
- src/*
drafts: false
Variables
# Inline variables
variables:
buildConfiguration: 'Release'
dotnet
# Variable groups (from Library)
variables:
- group: my-secrets
- group: my-config
# Template variables
variables:
- template: variables/common.yml
- name: environment
value: 'production'
# Conditional variables
variables:
${{ if eq(variables['Build.SourceBranch'], 'refs/heads/main') }}:
environment: 'production'
${{ else }}:
environment: 'staging'
# Runtime expressions
variables:
- name: fullPath
value: $(Build.SourcesDirectory)/$(projectName)
Jobs and Steps
jobs:
- job: Build
displayName: 'Build Application'
timeoutInMinutes: 60
pool:
vmImage: 'ubuntu-latest'
steps:
# Checkout
- checkout: self
clean: true
fetchDepth: 0
# Script tasks
- script: |
echo "Building..."
npm install
npm run build
displayName: 'Build with npm'
# PowerShell
- powershell: |
Write-Host "Running PowerShell"
displayName: 'PowerShell script'
# Bash
- bash: |
echo "Running Bash"
displayName: 'Bash script'
# Task shorthand
- task: UseDotNet@2
inputs:
# Download artifact
- download: current
artifact: drop
# Publish artifact
- publish: $(Build.ArtifactStagingDirectory)
artifact: drop
Multi-Stage Pipeline
stages:
- stage: Build
jobs:
- job: BuildJob
steps:
- task: DotNetCoreCLI@2
inputs:
command: 'publish'
publishWebProjects: true
arguments: '--configuration Release --output $(Build.ArtifactStagingDirectory)'
- publish: $(Build.ArtifactStagingDirectory)
artifact: webapp
- stage: DeployDev
displayName: 'Deploy to Dev'
dependsOn: Build
jobs:
- deployment: DeployDev
environment: 'dev'
strategy:
runOnce:
deploy:
steps:
- download: current
artifact: webapp
- task: AzureWebApp@1
inputs:
azureSubscription: 'Azure-Connection'
appName: 'myapp-dev'
package: '$(Pipeline.Workspace)/webapp/**/*.zip'
- stage: DeployProd
displayName: 'Deploy to Production'
dependsOn: DeployDev
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: DeployProd
environment: 'production'
strategy:
runOnce:
deploy:
steps:
- download: current
artifact: webapp
- task: AzureWebApp@1
inputs:
azureSubscription: 'Azure-Connection'
appName: 'myapp-prod'
package: '$(Pipeline.Workspace)/webapp/**/*.zip'
Templates
Step Template
# templates/build-steps.yml
parameters:
- name: buildConfiguration
type: string
default: 'Release'
- name: projects
type: string
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 }}'
Job Template
# templates/build-job.yml
parameters:
- name: name
type: string
- name: pool
type: object
default:
vmImage: 'ubuntu-latest'
jobs:
- job: ${{ parameters.name }}
pool: ${{ parameters.pool }}
steps:
- template: build-steps.yml
parameters:
buildConfiguration: 'Release'
projects: '**/*.csproj'
Stage Template
# templates/deploy-stage.yml
parameters:
- name: environment
type: string
- name: azureSubscription
type: string
- name: appName
type: string
stages:
- stage: Deploy_${{ parameters.environment }}
displayName: 'Deploy to ${{ parameters.environment }}'
jobs:
- deployment: Deploy
environment: ${{ parameters.environment }}
strategy:
runOnce:
deploy:
steps:
- task: AzureWebApp@1
inputs:
azureSubscription: ${{ parameters.azureSubscription }}
appName: ${{ parameters.appName }}
package: '$(Pipeline.Workspace)/**/*.zip'
Using Templates
# azure-pipelines.yml
trigger:
- main
stages:
- stage: Build
jobs:
- template: templates/build-job.yml
parameters:
name: BuildApp
- template: templates/deploy-stage.yml
parameters:
environment: 'dev'
azureSubscription: 'Azure-Dev'
appName: 'myapp-dev'
- template: templates/deploy-stage.yml
parameters:
environment: 'prod'
azureSubscription: 'Azure-Prod'
appName: 'myapp-prod'
Common Tasks
Docker
- task: Docker@2
inputs:
containerRegistry: 'myacr'
repository: 'myapp'
command: 'buildAndPush'
Dockerfile: '**/Dockerfile'
tags: |
$(Build.BuildId)
latest
Kubernetes
- task: KubernetesManifest@0
inputs:
action: 'deploy'
kubernetesServiceConnection: 'aks-connection'
namespace: 'default'
manifests: |
manifests/deployment.yml
manifests/service.yml
containers: |
myacr.azurecr.io/myapp:$(Build.BuildId)
Terraform
- task: TerraformInstaller@0
inputs:
terraform
- task: TerraformTaskV4@4
inputs:
provider: 'azurerm'
command: 'init'
workingDirectory: '$(System.DefaultWorkingDirectory)/terraform'
backendServiceArm: 'Azure-Connection'
backendAzureRmResourceGroupName: 'tfstate-rg'
backendAzureRmStorageAccountName: 'tfstatestorage'
backendAzureRmContainerName: 'tfstate'
backendAzureRmKey: 'terraform.tfstate'
- task: TerraformTaskV4@4
inputs:
provider: 'azurerm'
command: 'apply'
workingDirectory: '$(System.DefaultWorkingDirectory)/terraform'
environmentServiceNameAzureRM: 'Azure-Connection'
Testing
- task: DotNetCoreCLI@2
inputs:
command: 'test'
projects: '**/*Tests.csproj'
arguments: '--configuration $(buildConfiguration) --collect:"XPlat Code Coverage"'
- task: PublishCodeCoverageResults@1
inputs:
codeCoverageTool: 'Cobertura'
summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml'
Environments and Approvals
# Environment with approvals (configure in Azure DevOps UI)
jobs:
- deployment: DeployProd
environment: 'production' # Requires approval
strategy:
runOnce:
deploy:
steps:
- script: echo 'Deploying to production'
# Deployment strategies
strategy:
canary:
increments: [10, 20, 50, 100]
deploy:
steps:
- script: echo 'Canary deployment'
strategy:
rolling:
maxParallel: 2
deploy:
steps:
- script: echo 'Rolling deployment'
Best Practices
- Use Templates for reusable pipeline components
- Separate Stages for build, test, and deploy
- Use Variable Groups for secrets and shared config
- Enable Branch Policies for PR validation
- Use Environments with approval gates
- Cache Dependencies to speed up builds
- Use Service Connections for Azure resources
- Pin Task Versions to avoid breaking changes