In an Azure Pipelines deployment job, which lifecycle hooks can a strategy define, and when does each one run?
answer
- prepare, install, shift traffic, watch
- the last hook depends on the outcome
- same names, different repeat count
- batches for VMs, percentages for canary
- rollback has a home of its own
basics
~10 sAzure Pipelines deployment strategies expose preDeploy, deploy, routeTraffic and postRouteTraffic hooks, run in that order, plus on: success and on: failure hooks that run last depending on the outcome.
solid answer
~40 sAll three strategies — `runOnce`, `rolling` and `canary` — share the same hook vocabulary. `preDeploy` runs once per iteration for setup, such as draining a machine from the load balancer. `deploy` does the actual install. `routeTraffic` shifts traffic onto the new version. `postRouteTraffic` is where you sit and observe: soak the new version, watch health, run smoke tests. Then exactly one of `on: success` or `on: failure` runs — that is where a rollback belongs. What differs between strategies is *how often* the sequence repeats. `runOnce` executes it once. `rolling` repeats it over batches of virtual-machine resources, sized by `maxParallel`. `canary` repeats it once per entry in `increments`, so `increments: [10, 20]` gives two partial passes plus the final full rollout, with `$(strategy.increment)` available to the steps.
code
yaml · 24 lines- deployment: RollWeb
environment:
name: production
resourceType: VirtualMachine
tags: web
strategy:
rolling:
maxParallel: 2
preDeploy:
steps:
- script: ./drain-from-lb.sh
deploy:
steps:
- script: ./install.sh
routeTraffic:
steps:
- script: ./add-to-lb.sh
postRouteTraffic:
steps:
- script: ./smoke-test.sh
on:
failure:
steps:
- script: ./rollback.shgo deeper
Learn the four hook names and their order — preDeploy, deploy, routeTraffic, postRouteTraffic — and that on: failure runs when something goes wrong.
Explain that all three strategies share the hooks and differ only in how many times the sequence repeats: once for runOnce, per maxParallel batch for rolling, per increments entry for canary.
Show you have operated one: describe the mixed-fleet state a failed rolling batch leaves behind, and insist that on: failure steps be idempotent rather than assuming a clean abort.
Own where the abort decision comes from. The hooks give you a soak window; deciding what signal it watches and what threshold stops the rollout is a reliability call, and a sleep with no check is a gate in name only.
## One vocabulary, three cadences Azure Pipelines does something the other major CI platforms do not: it gives a deployment a *shape*. Rather than one opaque block of steps, a deployment job declares named phases, and the platform decides how many times to run them. The hooks, in execution order: 1. **`preDeploy`** — preparation. Take the target out of the load balancer, stop a service, snapshot a database, announce the deploy. 2. **`deploy`** — the change itself. Copy files, apply a manifest, run the installer. 3. **`routeTraffic`** — send live traffic to the new version. Return the machine to the load balancer, shift a Kubernetes service selector, adjust weights. 4. **`postRouteTraffic`** — the soak window. Traffic is flowing and you are watching. Smoke tests, health polling, a deliberate wait. 5. **`on: success` / `on: failure`** — exactly one of the two runs at the end. Failure is where rollback logic goes; success is where you clean up or notify. Each hook is a list of steps and can carry its own `pool:`, which is genuinely useful: you can soak from a hosted agent while deploying onto self-hosted VM resources. ## runOnce ```yaml strategy: runOnce: deploy: steps: - script: ./install.sh ``` The sequence executes once. This is the default choice and covers most deployments, including anything where the underlying platform — a Kubernetes Deployment, an App Service slot swap — is already doing the gradual part for you. ## rolling ```yaml strategy: rolling: maxParallel: 2 deploy: steps: - script: ./install.sh ``` The whole hook sequence repeats over batches of the environment's **virtual-machine** resources. `maxParallel` sets the batch size and accepts a count or a percentage such as `50%`. Rolling targets VM resources; there is no `pool:` on the job, because the registered machines are themselves the agents. Practical consequence: with `maxParallel: 2` across six machines, `preDeploy` through `postRouteTraffic` runs three times, and a failure in batch two leaves batch one on the new version and batches two and three on the old one. Your `on: failure` hook has to cope with a **mixed fleet**, which is the part people forget. ## canary ```yaml strategy: canary: increments: [10, 20] deploy: steps: - script: ./deploy.sh --percent $(strategy.increment) ``` The sequence repeats once per entry in `increments`, and the current value is exposed to steps as `$(strategy.increment)`. `increments: [10, 20]` means a pass at 10%, a pass at 20%, then the final full rollout — three iterations, not two. Canary is aimed at Kubernetes resources, where the increment is used to size a canary workload alongside the stable one. ## Where the judgment lives The hooks are mechanism, not policy. `postRouteTraffic` gives you a place to stand while traffic flows, but it does not tell you what to measure or what threshold aborts the rollout — that decision belongs to whoever owns the service's reliability targets. Two failure modes recur. A `postRouteTraffic` hook that only sleeps is theatre: it burns minutes and proves nothing, because nothing checks a signal. And an `on: failure` hook that assumes the deployment failed cleanly will make things worse when it fires halfway through a rolling batch, so write rollback steps to be idempotent and to tolerate a partially updated fleet. ## Reading it back In the run's logs the hooks appear as separate named phases per iteration, which makes a failed deploy much easier to read than a single 400-line step: you can see at a glance whether the failure was during install or after traffic arrived. That legibility is a real, underrated benefit of using the hooks rather than jamming everything into `deploy`.
- With rolling and `maxParallel: 2` across six machines, what state are you in if batch two fails?Two machines are on the new version, two failed mid-update, and two never started — a mixed fleet serving two versions. The `on: failure` hook fires, so rollback steps must be idempotent and must handle machines in all three states. Assuming a clean all-or-nothing failure is how a bad deploy becomes an outage.
- What does `increments: [10, 20]` actually produce in a canary strategy?Three iterations, not two: a pass at 10%, a pass at 20%, and then the final full rollout. Each iteration runs the whole hook sequence, and `$(strategy.increment)` carries the current value into the steps so they can size the canary workload accordingly.
- Why give postRouteTraffic its own pool?Because the observation work is different from the install work. With VM resources the deploy hooks run on the machines themselves, while a soak that polls a health endpoint or queries a monitoring API is better run from a hosted agent with outbound access. Each hook accepts its own `pool:`, so you are not forced to pick one agent for both.
saying these in an interview costs you the question
- Believing routeTraffic exists only in canary
- Thinking increments: [10, 20] means two total iterations
- Putting rollback in deploy instead of on: failure
- Treating postRouteTraffic as a sleep with no health check
- Assuming rolling works against Kubernetes resources