skip to content

In an Azure Pipelines deployment job, which lifecycle hooks can a strategy define, and when does each one run?

level: middleimportance: should knowfreq 45%

answer

  1. prepare, install, shift traffic, watch
  2. the last hook depends on the outcome
  3. same names, different repeat count
  4. batches for VMs, percentages for canary
  5. rollback has a home of its own

basics

~10 s

Azure 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 s

All 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
yaml
- 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.sh

go deeper

for a junior

Learn the four hook names and their order — preDeploy, deploy, routeTraffic, postRouteTraffic — and that on: failure runs when something goes wrong.

for a middle

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.

for a senior

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.

for a principal

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

context