Develop pipelines by using YAML
Develop pipelines by using YAML
A YAML pipeline is a file in your repository. That single fact drives everything else in this objective: the pipeline is versioned, reviewed and branched exactly like the code it builds, and a change to it travels through a pull request rather than through a portal.
The four levels
Every YAML pipeline is the same nesting, whether or not you write all of it:
| Level | Runs on | Purpose |
|---|---|---|
| Stage | — | A major division: build, test, deploy to an environment |
| Job | One agent | A unit of work scheduled to a single agent |
| Step | The job's agent | One script, task, powershell, bash or checkout |
| Deployment job | One agent, against an environment | A job with a deployment strategy and lifecycle hooks |
You can omit levels. A pipeline that is only steps: is legal — Azure Pipelines
implies a single job in a single stage.
The default that catches people out
Jobs in a stage run in parallel by default. Ordering only happens when you set
dependsOn. Every pipeline must contain at least one job with no dependencies, or there is nothing to start with.
jobs:
- job: A
steps:
- script: echo "A and B start together"
- job: B
steps:
- script: echo "no dependsOn, so B does not wait for A"
- job: C
dependsOn: [A, B] # C waits for BOTH
condition: succeeded() # ...and only if both succeeded
steps:
- script: echo "C runs last"Note the asymmetry worth remembering: job dependencies are not supported in classic release pipelines — multiple jobs there run in sequence.
Conditions
dependsOn decides order. condition decides whether. Setting a custom
condition replaces the implicit succeeded(), which is why a bare
condition: always() will run a step even after a cancellation.
| Condition | Runs when |
|---|---|
succeeded() | The default — all dependencies succeeded |
failed() | A dependency failed. Use for rollback and notification jobs |
always() | Always, including after cancellation. Use for cleanup |
canceled() | The run was cancelled |
succeededOrFailed() | Finished either way, but not cancelled |
Combine them with the expression functions:
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))Deployment jobs and strategies
A deployment job targets an environment, and that is what gives you
deployment history and the place where checks and approvals attach.
jobs:
- deployment: DeployWeb
environment: production
strategy:
runOnce:
preDeploy:
steps:
- script: echo "initialise"
deploy:
steps:
- script: echo "deploy the app"
routeTraffic:
steps:
- script: echo "shift traffic to the new version"
postRouteTraffic:
steps:
- script: echo "watch health before declaring success"
on:
failure:
steps:
- script: echo "roll back"
success:
steps:
- script: echo "clean up"Three strategies exist, and the difference is examinable:
| Strategy | Behaviour | Constraint |
|---|---|---|
runOnce | Each lifecycle hook executes once | The simplest, and the default choice |
rolling | Replaces instances in batches of maxParallel | VM resources only |
canary | Ships to a small slice, then increases | Used with increments |
Under rolling, the hooks run once per batch, not once per deployment.
Reuse: two different template mechanisms
# Insert — pulls content INTO your pipeline
steps:
- template: templates/npm-steps.yml
parameters:
vmImage: ubuntu-latest
# Extend — your pipeline is CONSTRAINED BY the template
extends:
template: templates/secure-pipeline.yml
parameters:
buildSteps:
- script: npm citemplate: composes. extends: is the security control — the extended template
decides what the consuming pipeline is permitted to do, which is what makes it
the answer whenever a question mentions enforcing organisational policy on
pipelines someone else writes.
Parameters are typed, and the type is what makes an extends-template enforceable:
parameters:
- name: buildSteps
type: stepList # only steps may be injected here
default: []What to carry into the exam
- Stages → jobs → steps, and a
deploymentjob is a job with a strategy. - Jobs are parallel until you write
dependsOn. - A custom
conditionreplacessucceeded();always()survives cancellation. rollingis VM-only and its hooks run per batch.extendsconstrains,templatecomposes.- Typed parameters (
stepList,jobList,stageList,object,string) are what let an extends-template enforce policy rather than merely suggest it.
Primary sources
- https://learn.microsoft.com/en-us/azure/devops/pipelines/process/phases
- https://learn.microsoft.com/en-us/azure/devops/pipelines/process/conditions
- https://learn.microsoft.com/en-us/azure/devops/pipelines/process/deployment-jobs
- https://learn.microsoft.com/en-us/azure/devops/pipelines/process/templates