Stage 5 · Platform
Infrastructure as Code on Azure
Azure DevOps Pipelines
YAML pipelines, environments, approvals, and multi-stage deployments.
Azure DevOps Pipelines
Azure DevOps Pipelines is a CI/CD service that automates builds, tests, and deployments. YAML pipelines define the entire workflow as code, stored in your repository alongside the application.
- Build pipelines — Compile code, run tests, produce artifacts
- Release pipelines — Deploy artifacts to environments with approvals
- Agent pools — Self-hosted or Microsoft-hosted build agents
- Service connections — Authenticate to Azure, Docker Hub, or other services
YAML Pipeline Structure
trigger:
branches:
include:
- main
- feature/*
pool:
vmImage: 'ubuntu-latest'
variables:
buildConfiguration: 'Release'
azureSubscription: 'my-service-connection'
stages:
- stage: Build
displayName: 'Build and Test'
jobs:
- job: Build
displayName: 'Build Application'
steps:
- task: DotNetCoreCLI@2
displayName: 'Restore packages'
inputs:
command: 'restore'
projects: '**/*.csproj'
- task: DotNetCoreCLI@2
displayName: 'Build and test'
inputs:
command: 'build'
projects: '**/*.csproj'
arguments: '--configuration $(buildConfiguration) --no-restore'
- task: PublishBuildArtifacts@1
displayName: 'Publish artifacts'
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'drop'The trigger section defines which branches start a build. The pool defines the build agent. Stages contain jobs, and jobs contain steps.
Stages and Jobs
stages:
- stage: Build
displayName: 'Build'
jobs:
- job: BuildApp
steps:
- script: echo "Building application"
- stage: Test
displayName: 'Integration Tests'
dependsOn: Build
jobs:
- job: IntegrationTests
steps:
- script: echo "Running integration tests"
- stage: DeployDev
displayName: 'Deploy to Dev'
dependsOn: Test
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: DeployToDev
environment: 'development'
strategy:
runOnce:
deploy:
steps:
- script: echo "Deploying to dev"Stages run sequentially by default. Use dependsOn to define dependencies. The condition on DeployDev ensures it only runs on the main branch.
Deployment jobs (deployment: instead of job:) enable environment tracking, approval gates, and deployment history in the Azure DevOps UI.
Environments
resources:
pipelines: []
stages:
- stage: DeployProd
displayName: 'Deploy to Production'
dependsOn: DeployStaging
jobs:
- deployment: DeployToProd
environment: 'production'
strategy:
runOnce:
deploy:
steps:
- task: HelmInstaller@1
displayName: 'Install Helm'
inputs:
helmVersion: '3.12.0'
- script: |
helm upgrade --install payments ./charts/payments \
--namespace production \
--set image.tag=$(Build.BuildId) \
--wait --timeout 300sEnvironments in Azure DevOps track deployment history, approvals, and checks. Define them in the YAML or configure them in the UI.
Approvals and Gates
Approvals require manual sign-off before a deployment proceeds. Gates can automatically check conditions like Azure Policy compliance, monitoring metrics, or external API responses.
stages:
- stage: DeployStaging
displayName: 'Deploy to Staging'
jobs:
- deployment: DeployToStaging
environment: 'staging'
strategy:
runOnce:
deploy:
steps:
- script: echo "Deploying to staging"
- stage: ApprovalGate
displayName: 'Wait for Approval'
dependsOn: DeployStaging
jobs:
- job: WaitForApproval
pool: server
steps:
- task: ManualValidation@0
displayName: 'Approve Production Deployment'
timeoutInMinutes: 1440
instructions: 'Review staging deployment and approve for production'
onTimeout: 'reject'
notifyUsers: 'admin@contoso.com,lead@contoso.com'
- stage: DeployProd
displayName: 'Deploy to Production'
dependsOn: ApprovalGate
condition: succeeded()
jobs:
- deployment: DeployToProd
environment: 'production'
strategy:
runOnce:
deploy:
steps:
- script: echo "Deploying to production"ManualValidation pauses the pipeline and waits for an authorized user to approve or reject. The timeoutInMinutes defines how long to wait before auto-rejecting.
AKS Deployment Pipeline
trigger:
branches:
include: [main]
paths:
include: ['src/*', 'charts/*']
pool:
vmImage: 'ubuntu-latest'
variables:
acrName: 'craksprodeastus'
aksCluster: 'aks-payments-prod'
resourceGroup: 'rg-aks-prod'
stages:
- stage: Build
jobs:
- job: BuildAndPush
steps:
- task: Docker@2
displayName: 'Build image'
inputs:
containerRegistry: 'acr-connection'
repository: 'payments'
command: 'buildAndPush'
Dockerfile: 'src/Dockerfile'
tags: '$(Build.BuildId)'
- stage: Deploy
dependsOn: Build
jobs:
- deployment: DeployToAKS
environment: 'production'
strategy:
runOnce:
deploy:
steps:
- task: HelmInstaller@1
inputs: { helmVersion: '3.12.0' }
- script: |
helm upgrade --install payments ./charts/payments \
--namespace production \
--set image.repository=$(acrName).azurecr.io/payments \
--set image.tag=$(Build.BuildId) \
--wait --timeout 300sThe pipeline builds a Docker image, pushes to ACR, and deploys to AKS via Helm. The Build.BuildId tag creates a unique, traceable image tag.
Always use immutable image tags (Build.BuildId, git SHA, semantic version). The :latest tag can be overwritten, making rollbacks unreliable and audits impossible.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.