Azure DevOps Pipelines: YAML CI/CD from Build to Production
Classic vs YAML Pipelines
Azure DevOps offers two pipeline types. Classic (GUI) pipelines are being deprecated — the YAML format is the right choice for any new pipeline. Benefits:
- Stored in version control alongside code
- Reviewable via pull requests
- Reusable templates across projects
- Full audit trail of pipeline changes
Basic YAML Pipeline Structure
# azure-pipelines.yml (at repo root)
trigger:
branches:
include:
- main
- develop
pr:
branches:
include:
- main
pool:
vmImage: ubuntu-latest
variables:
imageName: 'myapp'
containerRegistry: 'productionregistry.azurecr.io'
stages:
- stage: Build
jobs:
- job: BuildAndTest
steps:
- task: NodeTool@0
inputs:
versionSpec: '20.x'
- script: npm ci
displayName: Install dependencies
- script: npm test
displayName: Run tests
- script: npm run build
displayName: Build application
Multi-Stage Pipeline
Real pipelines have multiple stages. Each stage can have deployment approvals:
stages:
- stage: CI
displayName: Build & Test
jobs:
- job: Build
pool:
vmImage: ubuntu-latest
steps:
- script: npm ci && npm test && npm run build
- task: PublishPipelineArtifact@1
inputs:
targetPath: './dist'
artifact: 'dist'
- stage: BuildImage
displayName: Build Docker Image
dependsOn: CI
jobs:
- job: DockerBuild
steps:
- task: Docker@2
displayName: Build and push image
inputs:
command: buildAndPush
repository: $(imageName)
dockerfile: Dockerfile
containerRegistry: 'production-acr-service-connection'
tags: |
$(Build.BuildId)
latest
- stage: DeployStaging
displayName: Deploy to Staging
dependsOn: BuildImage
jobs:
- deployment: DeployStaging
environment: staging
strategy:
runOnce:
deploy:
steps:
- task: KubernetesManifest@1
inputs:
action: deploy
kubernetesServiceConnection: 'staging-aks'
manifests: k8s/*.yaml
containers: |
$(containerRegistry)/$(imageName):$(Build.BuildId)
- stage: DeployProduction
displayName: Deploy to Production
dependsOn: DeployStaging
jobs:
- deployment: DeployProduction
environment: production # Has approval gate configured
strategy:
runOnce:
deploy:
steps:
- task: KubernetesManifest@1
inputs:
action: deploy
kubernetesServiceConnection: 'production-aks'
manifests: k8s/*.yaml
containers: |
$(containerRegistry)/$(imageName):$(Build.BuildId)
Environments and Approvals
Environments track deployments and optionally require approvals before a deployment runs.
- In Azure DevOps, go to Pipelines → Environments → New environment
- Name it
production - Click Approvals and checks → + → Approvals
- Add approvers (individuals or groups) and set timeout
When a pipeline hits a deployment job targeting production, it pauses and sends approval notifications to the configured approvers. No YAML changes needed.
Variable Groups and Key Vault Integration
Store reusable variables in Pipelines → Library → Variable Groups:
variables:
- group: production-secrets # Links to a Library variable group
- name: imageName
value: myapp
Link a variable group directly to Azure Key Vault for automatic secret sync:
- Library → New variable group → Link secrets from Azure Key Vault
- Select subscription and Key Vault
- Add the secrets you want available as variables
In the pipeline, use them like any variable: $(db-password).
Container Jobs
Run your pipeline steps inside a Docker container — eliminates "works on my machine" issues:
jobs:
- job: Test
container:
image: node:20-alpine
steps:
- script: npm ci
- script: npm test
- job: BuildGo
container:
image: golang:1.22-alpine
steps:
- script: go build ./...
- script: go test ./...
Use a matrix for multi-version testing:
jobs:
- job: TestMatrix
strategy:
matrix:
Node18:
nodeVersion: '18.x'
Node20:
nodeVersion: '20.x'
Node22:
nodeVersion: '22.x'
steps:
- task: NodeTool@0
inputs:
versionSpec: $(nodeVersion)
- script: npm ci && npm test
Reusable Templates
Templates let you share pipeline logic across repos. Create a templates/ folder:
# templates/build-node.yml
parameters:
- name: nodeVersion
type: string
default: '20.x'
- name: testCommand
type: string
default: 'npm test'
steps:
- task: NodeTool@0
inputs:
versionSpec: ${{ parameters.nodeVersion }}
displayName: Use Node.js ${{ parameters.nodeVersion }}
- script: npm ci
displayName: Install dependencies
- script: ${{ parameters.testCommand }}
displayName: Run tests
- script: npm run build
displayName: Build
- task: PublishTestResults@2
inputs:
testResultsFormat: JUnit
testResultsFiles: '**/test-results.xml'
Reference the template:
# azure-pipelines.yml
stages:
- stage: CI
jobs:
- job: Build
steps:
- template: templates/build-node.yml
parameters:
nodeVersion: '20.x'
testCommand: 'npm run test:ci'
For templates in another repository:
resources:
repositories:
- repository: templates
type: git
name: DevOps/pipeline-templates
ref: refs/heads/main
stages:
- stage: CI
jobs:
- template: node/build.yml@templates
Deployment Strategies
Rolling Update
strategy:
rolling:
maxParallel: 2
deploy:
steps:
- script: ./deploy.sh
Blue-Green
strategy:
runOnce:
preDeploy:
steps:
- script: ./switch-to-blue.sh
deploy:
steps:
- script: ./deploy-to-inactive.sh
routeTraffic:
steps:
- script: ./switch-traffic.sh
postRouteTraffic:
steps:
- script: ./smoke-tests.sh
on:
failure:
steps:
- script: ./rollback.sh
success:
steps:
- script: ./cleanup-old-slot.sh
Canary
strategy:
canary:
increments: [10, 20, 50, 100] # Deploy to 10%, then 20%, etc.
preDeploy:
steps:
- script: echo "Pre-deploy checks"
deploy:
steps:
- task: KubernetesManifest@1
inputs:
action: deploy
strategy: canary
percentage: $(System.StageAttempt)
postRouteTraffic:
steps:
- script: ./run-smoke-tests.sh
Service Connections
Service connections are how Azure DevOps authenticates to external services. Common types:
Azure Resource Manager (for Azure deployments):
- Project Settings → Service connections → New → Azure Resource Manager
- Use "Workload Identity federation" (no secret to rotate) or "Service principal"
- Scope to subscription or resource group
Docker Registry (for ACR or Docker Hub):
- New → Docker Registry
- Select Azure Container Registry and subscription — no credentials needed
Kubernetes (for kubectl tasks):
- New → Kubernetes
- Select Azure subscription and AKS cluster — uses kubeconfig
Publish Test Results and Code Coverage
steps:
- script: npm run test:ci -- --reporter=junit --coverage
displayName: Run tests with coverage
- task: PublishTestResults@2
condition: succeededOrFailed()
inputs:
testResultsFormat: JUnit
testResultsFiles: '**/junit-report.xml'
testRunTitle: 'Unit Tests - Node $(nodeVersion)'
failTaskOnFailedTests: true
- task: PublishCodeCoverageResults@2
inputs:
codeCoverageTool: Cobertura
summaryFileLocation: coverage/cobertura-coverage.xml
reportDirectory: coverage/lcov-report
Results appear in the pipeline run under Tests and Code Coverage tabs.
Caching Dependencies
variables:
npm_config_cache: $(Pipeline.Workspace)/.npm
steps:
- task: Cache@2
inputs:
key: 'npm | "$(Agent.OS)" | package-lock.json'
restoreKeys: |
npm | "$(Agent.OS)"
path: $(npm_config_cache)
displayName: Cache npm packages
- script: npm ci
displayName: Install (uses cache if hit)
Cache hit skips the npm ci download from npm registry, reducing build time from minutes to seconds on warm runs.
Was this article helpful?
CI/CD Engineering Lead
Automation evangelist who believes no deployment should require a human. I write pipelines, break pipelines, and write about both. Code-first, always.
Related Articles
Azure DevOps Pipelines: YAML Templates and Best Practices
Master Azure DevOps YAML pipelines with reusable templates, environment approvals, and multi-stage deployments.
Jenkins Declarative Pipelines: From Zero to Production CI/CD
How to write Jenkins declarative pipelines from scratch — stages, agents, environment variables, credentials, parallel execution, post conditions, and shared libraries. Practical Jenkinsfile patterns for real projects.
The Complete Guide to GitHub Actions CI/CD: From Zero to Production-Ready Pipelines
Build production-grade GitHub Actions CI/CD pipelines — from first workflow to reusable workflows, matrix builds, and deployment gates.
Azure AKS: Production Kubernetes Cluster Setup and Configuration
Deploy a production AKS cluster on Azure — node pools, managed identities, Azure CNI, RBAC integration with Entra ID, ACR integration, autoscaling, monitoring with Azure Monitor, and Terraform setup.
Azure Core Services: The DevOps Engineer's Essential Guide
Understand Azure's essential services — VMs, Storage, VNets, Azure AD (Entra ID), AKS, App Service, and Azure DevOps for infrastructure automation.
Jenkins Installation & Configuration: From Zero to First Pipeline
Install Jenkins on Ubuntu and Docker, configure security settings, manage plugins, and create your first freestyle and pipeline jobs step by step.
More in Azure
View all →Azure Blob Storage 403 AuthorizationPermissionMismatch: Step-by-Step Fix Guide
If you've ever stared at this error message in your logs, you know the frustration: It's one of those errors that looks simple on the surface but has about...
Fix Azure 'SubscriptionNotRegistered' Error
Resolve the Azure 'SubscriptionNotRegistered for resource type' error by registering the required resource provider with step-by-step instructions.
Azure AKS: Production-Ready Cluster Setup in 15 Minutes
Stand up a production-grade AKS cluster with autoscaling, RBAC, monitoring, and private networking in under 15 minutes.