Controlling Azure DevOps Pipeline Jobs with Branch and Environment Conditions
Use condition expressions in Azure Pipelines YAML to skip production deployments on feature branches and target specific environments only from main or release branches.
07 Aug 2026, 04:33 UTC

Controlling Azure DevOps Pipeline Jobs with Branch and Environment Conditions
When you need a production deployment to run only from main but keep building and testing every branch, you do not need separate pipeline files. In Azure Pipelines YAML, add a condition expression directly beneath the job or stage you want to gate. The expression evaluates at queue time using predefined variables such as Build.SourceBranch, letting you skip deploy jobs on feature branches or restrict staging to main and release branches.
How job conditions evaluate
A condition is a Boolean expression evaluated after the pipeline initializes variables but before Azure DevOps dispatches the job to an agent. If the expression returns false, the job is skipped; it does not fail. The condition can reference predefined variables like Build.SourceBranch (which contains the full ref, for example refs/heads/main), Build.Reason, or variables you define in the variables block.
Conditions support functions including and(), or(), not(), eq(), ne(), and startsWith(). Every function call requires parentheses. When you use dependsOn to chain jobs or stages, include succeeded() in the condition so the gated job only runs when upstream work completed successfully.
A worked pipeline example
The following pipeline builds and tests every branch, deploys to a staging environment from main and release branches, and deploys to production only from main.
trigger:
branches:
include:
- main
- release/*
- feature/*
stages:
- stage: Build
jobs:
- job: BuildAndTest
pool:
vmImage: 'ubuntu-latest'
steps:
- script: echo "Building and running tests"
- stage: DeployStaging
condition: and(succeeded(), not(startsWith(variables['Build.SourceBranch'], 'refs/heads/feature/')))
jobs:
- deployment: DeployStaging
displayName: Deploy to staging
environment: staging
pool:
vmImage: 'ubuntu-latest'
strategy:
runOnce:
deploy:
steps:
- script: echo "Deploying to staging environment"
- stage: DeployProduction
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: DeployProduction
displayName: Deploy to production
environment: production
pool:
vmImage: 'ubuntu-latest'
strategy:
runOnce:
deploy:
steps:
- script: echo "Deploying to production environment"
In this example, BuildAndTest has no condition, so it always runs. DeployStaging uses not(startsWith(...)) to exclude any branch under refs/heads/feature/. DeployProduction uses eq(...) to match the exact main branch ref. Both deployment jobs use the environment property to associate the run with an Azure DevOps Environment, which enables approval checks and deployment history without changing the condition logic.
Using variables for environment gating
If your team promotes the same artifact through multiple environments, you can gate stages on a pipeline variable instead of hard-coding branch names. Define the variable at queue time or in the YAML variables block, then compare it in the condition.
variables:
targetEnvironment: staging
stages:
- stage: Deploy
condition: eq(variables['targetEnvironment'], 'production')
jobs:
- deployment: DeployProd
environment: production
strategy:
runOnce:
deploy:
steps:
- script: echo "Deploying to ${{ variables.targetEnvironment }}"
When you queue the pipeline manually, override targetEnvironment in the Run pipeline dialog. The condition evaluates the overridden value, so the same YAML can deploy to different environments based on how the run was triggered.
Limits and common mistakes
Indentation errors
The condition property must align with other job or stage properties such as pool, steps, or dependsOn. Placing it under steps or at the wrong nesting level causes a YAML validation error, or the condition is silently ignored and the job always runs.
Variable availability
Conditions evaluate before the job starts. You can reference predefined variables and variables defined in the variables block, but you cannot read output variables set by a previous job in the same run. If you need to pass data between jobs, use dependsOn with output variables inside steps, not in the condition itself.
Expression syntax
Missing parentheses around function arguments cause validation errors. For example, and succeeded(), eq(...) is invalid; wrap the entire expression in parentheses: and(succeeded(), eq(...)). String comparisons are case-insensitive in Azure Pipelines, but branch names are case-sensitive on the Git server, so use the exact casing returned by Build.SourceBranch.
Skipped versus failed
A false condition skips the job. If a downstream job uses condition: always(), it will run even when an upstream job was skipped. Use succeeded() when you want skipped upstream jobs to block downstream work, or not(canceled()) when you want downstream jobs to run after a skip.
Environment resource versus condition
The environment property on a deployment job controls tracking, approvals, and resource locking. It does not by itself decide whether the job runs. You still need a condition to prevent the deployment from executing on unwanted branches.
Verifying the behavior
To confirm conditions work as intended, push a commit to a feature branch and inspect the pipeline run summary. The BuildAndTest job should complete, while DeployStaging and DeployProduction should appear as skipped. For a manual check, use Run pipeline, override targetEnvironment or select a different branch, and observe which stages execute. The Azure DevOps UI shows validation errors before you save the YAML, so correct syntax problems before committing.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.