Azure Pipelines is the CI/CD service in Azure DevOps (alongside Boards, Repos, Test Plans and Artifacts). It builds and deploys any language to any platform from GitHub, Azure Repos, Bitbucket or others. Concepts are in CI-CD Pipelines.
Azure DevOps services
| Service | Purpose |
|---|---|
| Azure Boards | Work items, backlogs, sprints |
| Azure Repos | Git repositories |
| Azure Pipelines | Build, test, deploy |
| Azure Test Plans | Manual and exploratory testing |
| Azure Artifacts | Package feeds (NuGet, npm, Maven, Python) |
Concepts
| Term | Meaning |
|---|---|
| Pipeline | The full definition (azure-pipelines.yml) |
| Trigger | trigger (CI), pr (pull request), schedules |
| Stage | Major phase, e.g. Build, Dev, Prod |
| Job | Runs on one agent; jobs in a stage run in parallel by default |
| Step | A script or a task |
| Task | Packaged action (DotNetCoreCLI@2, AzureWebApp@1, Docker@2) |
| Agent | Machine that runs a job; Microsoft-hosted (ubuntu-latest) or self-hosted |
| Agent pool | Group of agents |
| Service connection | Stored, secured link to Azure, AWS, Docker registry, Kubernetes, GitHub |
| Variable group | Shared variables, can link to Key Vault |
| Environment | Deployment target with approvals and checks |
| Artifact | Build output published with PublishPipelineArtifact |
Pipelines can be YAML (preferred, in the repo) or Classic (visual editor, legacy; Classic Release pipelines are being replaced by multi-stage YAML).
Multi-stage YAML Pipeline
# azure-pipelines.yml
trigger:
branches:
include: [main]
pr:
branches:
include: [main]
pool:
vmImage: ubuntu-latest
variables:
- group: shop-api-shared # variable group
- name: imageTag
value: $(Build.BuildId)
stages:
- stage: Build
jobs:
- job: BuildTest
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.12'
- script: |
pip install -r requirements.txt
pytest --junitxml=results.xml
displayName: Install and test
- task: PublishTestResults@2
condition: succeededOrFailed()
inputs:
testResultsFiles: results.xml
- task: Docker@2
inputs:
command: buildAndPush
containerRegistry: acr-connection # service connection
repository: shop-api
tags: $(imageTag)
- stage: Staging
dependsOn: Build
jobs:
- deployment: DeployStaging
environment: staging
strategy:
runOnce:
deploy:
steps:
- task: AzureWebAppContainer@1
inputs:
azureSubscription: azure-connection
appName: shop-api-staging
containers: myregistry.azurecr.io/shop-api:$(imageTag)
- stage: Production
dependsOn: Staging
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: DeployProd
environment: production # approvals configured on this environment
strategy:
runOnce:
deploy:
steps:
- task: AzureWebAppContainer@1
inputs:
azureSubscription: azure-connection
appName: shop-api-prod
containers: myregistry.azurecr.io/shop-api:$(imageTag)
Deployment targets: 2. Azure Compute (App Service, ACR, AKS, Functions).
Variables and Expressions
| Syntax | When resolved | Example |
|---|---|---|
$(var) |
Runtime macro | $(Build.BuildId) |
${{ variables.x }} |
Compile time template expression | conditional insertion |
$[ variables.x ] |
Runtime expression | in condition / variable definitions |
Common predefined variables: Build.BuildId, Build.SourceBranch, Build.SourceVersion, Pipeline.Workspace, System.DefaultWorkingDirectory.
Secrets: mark variables secret, or link a variable group to Key Vault (5. Azure Identity and Security). Secret variables are not passed to scripts automatically; map them through env:.
- script: ./deploy.sh
env:
API_KEY: $(apiKey)
Deployment Jobs and Strategies
deployment jobs target an environment and record history there.
| Strategy | Behaviour |
|---|---|
runOnce |
Single execution |
rolling |
Update a few targets at a time (VM environments) |
canary |
Increment traffic to the new version, e.g. 10, 20 percent |
Lifecycle hooks: preDeploy, deploy, routeTraffic, postRouteTraffic, on: failure / success.
Approvals and checks
Configure on the environment: manual approval, branch control, business hours, Azure Monitor alerts, required template. This gives gated production releases.
Templates (Reuse)
# templates/build.yml
parameters:
- name: pythonVersion
type: string
default: '3.12'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: ${{ parameters.pythonVersion }}
- script: pytest
# azure-pipelines.yml
steps:
- template: templates/build.yml
parameters:
pythonVersion: '3.11'
Templates can be step, job, stage or variable templates, and can live in another repo (resources.repositories).
Matrix and Caching
strategy:
matrix:
py311: { python.version: '3.11' }
py312: { python.version: '3.12' }
steps:
- task: Cache@2
inputs:
key: 'pip | "$(Agent.OS)" | requirements.txt'
path: $(Pipeline.Workspace)/.pip
Service Connections and Security
- Azure Resource Manager connection: prefer workload identity federation (OIDC) over client secrets
- Scope connections to a resource group where possible
- Restrict who can use connections and which pipelines may access them
- Use secure files for certificates / keystores
- Protect
mainwith branch policies (required builds, reviewers) - Use Microsoft-hosted agents for clean builds; self-hosted when you need private network access or special tools (
vmImagereplaced byname: pool)
Azure Pipelines vs GitHub Actions
| Azure Pipelines | GitHub Actions | |
|---|---|---|
| Config | azure-pipelines.yml, stages/jobs/tasks |
.github/workflows/*.yml, jobs/actions |
| Approvals | Environment checks | Environment protection rules |
| Reuse | Templates | Reusable workflows, composite actions |
| Ecosystem | Azure DevOps suite, tight Azure integration | Largest marketplace, tied to GitHub |
| Concepts map | Service connection ↔ OIDC / secrets, Variable group ↔ vars/secrets |
See GitHub Actions and Jenkins.