Azure DevOps Pipelines: Multi Stage YAML with Environments and Approvals

Every organization on Azure DevOps eventually has the same pipeline archaeology problem: forty repositories, each with a YAML file copy pasted from the one before it, drifting independently. Multi stage YAML done well solves deployment governance and repeatability in one design. Done badly it is classic pipelines with extra indentation. The difference is templates, environments, and how identity reaches Azure.

The Shape of a Good Pipeline

trigger:
  branches:
    include: [main]

resources:
  repositories:
    - repository: templates
      type: git
      name: platform/pipeline-templates
      ref: refs/tags/v2.3.0

stages:
  - stage: build
    jobs:
      - template: jobs/dotnet-build.yml@templates
        parameters:
          projectPath: src/Orders.Api

  - stage: deploy_test
    dependsOn: build
    jobs:
      - template: jobs/deploy-containerapp.yml@templates
        parameters:
          environment: orders-test
          appName: orders-api

  - stage: deploy_prod
    dependsOn: deploy_test
    jobs:
      - template: jobs/deploy-containerapp.yml@templates
        parameters:
          environment: orders-prod
          appName: orders-api

Everything real lives in a versioned central template repository, pinned by tag so template upgrades are deliberate, reviewable events instead of surprises across forty pipelines. Application pipelines shrink to parameters. When the platform team fixes the build template, teams adopt it by bumping one tag.

Environments: Where Governance Lives

Deployment jobs target environments, and environments carry checks that run before any stage touching them: manual approvals from named groups, business hours windows, Azure Monitor alert gates that block deployment while the service is unhealthy, and required template checks that enforce every deployment to production flows through the sanctioned template. This is the crucial inversion: governance attaches to the environment once, centrally, instead of being re-implemented in every pipeline. A developer can write whatever YAML they like; production still requires the approval, the health gate, and the blessed template.

jobs:
  - deployment: deploy
    environment: orders-prod
    strategy:
      runOnce:
        deploy:
          steps:
            - task: AzureCLI@2
              inputs:
                azureSubscription: sc-prod-oidc
                scriptType: bash
                scriptLocation: inlineScript
                inlineScript: |
                  az containerapp update \
                    --name $(appName) \
                    --resource-group rg-apps-prod \
                    --image $(acr)/$(appName):$(Build.BuildNumber)

Service Connections Without Secrets

Use workload identity federation for every Azure service connection. The connection maps to an Entra application with a federated credential trusting Azure DevOps as the issuer; tokens are exchanged at runtime and there is no client secret to expire on a Saturday or leak in a variable group. Scope connections tightly: one per environment per team, with the minimum RBAC role at the narrowest scope, never one god connection with Owner on the subscription. Pair that with variable groups linked to Key Vault for the application settings that genuinely are secrets, and your pipelines hold no credentials at all.

Habits That Keep It Healthy

Build once, deploy the same artifact everywhere; if your production stage rebuilds, you are testing one binary and shipping another. Keep stage conditions honest with dependsOn plus succeeded rather than clever custom conditions nobody can predict. Alert on pipeline duration trends, because a build that quietly grew from six minutes to twenty is a productivity tax everyone pays daily. And run your agents as scale set agents in your own VNet when pipelines need to reach private endpoints, which after this month of posts, everything should.

Tomorrow: the GitHub Actions equivalent, OIDC to Azure with no secrets anywhere.

Cheers
Osama

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.