Gating Azure DevOps YAML Deployments with Environment Approvals and Checks
Move deployment gating out of your YAML and onto Azure DevOps Environments: configure approvals, branch control, and external checks once, then verify each gate holds before trusting it in production.
23 Aug 2025, 18:36 UTC

The problem: YAML pipelines deploy on every green build
A multi-stage YAML pipeline that builds, tests, and deploys will happily push to production the moment any stage succeeds — including from a feature branch someone forgot to exclude. Azure DevOps Environments solve this by moving the gate out of the pipeline definition and onto the deployment target itself: the pipeline asks the environment for permission, and the environment decides based on approvals, branch rules, and external checks you configure once.
The useful mental model: approvals and checks live on the environment, not the pipeline. Any pipeline that targets that environment inherits the gate, which is exactly what you want when several pipelines deploy to the same production target.
What you need before starting
- An Azure DevOps project with an existing multi-stage YAML pipeline (this guide assumes Azure DevOps Services; check types differ on on-premises Server versions).
- Permission to create and manage environments — typically Project Administrator, or the Environment Administrator role on an existing environment.
- A service connection scoped to your deployment target (for example, an Azure Resource Manager connection limited to the production resource group).
- Roughly 15 minutes; no pipeline downtime is required because the gate only takes effect once the YAML references the environment.
Step 1: Create the environment and add checks
- In the Azure DevOps portal, go to Pipelines > Environments and choose New environment. Give it a name that matches how your team talks about the target, e.g.
production. You can add a resource (Kubernetes cluster or VM) now, or choose None and treat it as a logical target — approvals work either way. - Open the environment, then open the overflow menu and choose Approvals and checks.
- Add an Approvals check. Designate at least one approver (a group works better than an individual for coverage). Enable Allow approvers to approve their own runs only if your process permits it — for production, leave it off so the person who triggered the run cannot self-approve.
- Add a Branch control check and restrict allowed branches to
refs/heads/main(plus a release branch pattern if you use one). This blocks feature-branch deployments even if someone points the pipeline YAML at the environment by mistake. - Optionally add Business hours or an Invoke Azure Function / REST API check (for example, to verify no active incident is open in your status tool before allowing deployment).
Note the approval timeout: approvals expire after a configurable period (30 days by default), and a timed-out approval fails the deployment job rather than skipping it. For most teams a shorter timeout, such as 24–72 hours, surfaces stale requests faster.
Step 2: Wire the pipeline to the environment
A regular job cannot target an environment — you need a deployment job. Replace your production deploy step with something like this:
stages:\n- stage: Build\n jobs:\n - job: BuildJob\n steps:\n - script: dotnet build -c Release\n\n- stage: DeployProd\n dependsOn: Build\n jobs:\n - deployment: DeployWeb\n environment: production # must match the environment name exactly\n strategy:\n runOnce:\n deploy:\n steps:\n - task: AzureWebApp@1\n inputs:\n azureSubscription: 'prod-service-connection'\n appName: 'myapp-prod'\n package: '$(Pipeline.Workspace)/**/*.zip'Commit this on main (or the branch your branch-control check allows). No other pipeline change is required — the gate activates because the job now references the environment.
Step 3: Verify the gate actually holds
Do not assume the configuration works; test each control deliberately.
- Happy path: Trigger the pipeline from
main. TheDeployProdstage should show a Waiting state with a pending-approval banner before any deploy step runs. Approvers get a portal notification and an email. Approve it and confirm the stage proceeds. - Branch control: Push a topic branch and run the pipeline from it. The deployment job should fail the branch-control check. Open the environment and check the checks-evaluation log to see the rejection recorded — this log is also your audit trail later.
- History: After a successful run, open the environment's Deployments tab and confirm the run, commit, and pipeline appear. This traceability is the other payoff of environments.
- Self-approval: If you disabled self-approval, have the person who queued the run attempt to approve it and confirm they are blocked.
Recovery: what approvals do and don't give you
Approvals are a gate, not a rollback mechanism. If a bad deployment gets approved, your options are:
- Redeploy a previous run: from the environment's deployment history, find the last known-good run and re-run that pipeline. This redeploys the old artifact and is the fastest recovery for most app deployments.
- A dedicated rollback stage: for deployments where re-running isn't safe (database migrations, infrastructure), add a manually triggered stage that runs compensating steps, gated by its own approval.
If you need to remove the gate temporarily (for example, during an incident drill), edit the environment's checks rather than editing the pipeline — that keeps the change in one auditable place and avoids a stray YAML commit that outlives the emergency.
Limitations to know
- Classic release pipelines use pre-deployment approvals on stages; environment checks do not apply to them. The two models are separate.
- Available check types and the configuration UI have evolved over time, and on-premises Azure DevOps Server supports a narrower set. Verify against your version's documentation before relying on a specific check.
- Checks evaluate at job start, not continuously — a check that passes at approval time is not re-evaluated mid-deployment.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.