![]() |
| Azure DevOps pipeline flow |
If you've ever stared at an Azure DevOps YAML pipeline error wondering why your stage isn't triggering, or why a variable set in Job 1 is completely invisible in Job 2 — you're not alone. YAML pipelines are powerful, but their hierarchy and variable scoping trip up even experienced engineers.
In this tutorial, you'll get a clear, practical breakdown of how Azure DevOps YAML pipelines actually work — from the top-level stages down to individual steps, how to pass variables across tasks, jobs, and stages, how to configure CI and PR triggers correctly, and how to use conditions to control exactly when each part of your pipeline runs.
Whether you're setting up your first pipeline or trying to debug an existing one, this guide gives you the mental model and real code examples to get it right the first time.
Azure Devops YAML Pipeline Hierarchy
A YAML pipeline is structured in a hierarchical manner:
Stages – The top-level division in a pipeline (e.g., "Build," "Test," "Deploy").
Jobs – A sequence of steps that run sequentially or in parallel within a stage.
Steps – The smallest executable unit, which can be a script or a predefined task.
1. Stages in Azure DevOps yml pipeline
- Build App
- Run Tests
- Deploy to Production
Example:
stages:
- stage: Build App
jobs:
- job: BuildJob
steps:
- script: echo "Building the app..." 2. Jobs
A job is a set of steps that run together. Jobs can:
- Run sequentially or in parallel.
- Have dependencies (e.g.,
Job2depends onJob1). - Read more about job here
Example:
jobs:
- job: JobA
steps:
- script: echo "Running Job A"
- job: JobB
dependsOn: JobA
steps:
- script: echo "Running Job B after Job A" 3. Steps (Tasks)
Steps are the smallest executable units in a pipeline. They can be:
- Scripts (Bash, PowerShell, etc.)
- Predefined tasks (e.g.,
npm install,dotnet build)
Example:
steps:
- script: echo "Restoring dependencies..."
- task: DotNetCoreCLI@2
inputs:
command: 'build' Passing Variables Across Tasks, Jobs, and Stages of yaml pipeline
1. Pass variables accross Tasks in the Same Job
Use task.setvariable to pass values between tasks.
Example:
steps:
- powershell: |
Write-Host "##vso[task.setvariable variable=MYVAR]abc"
- script: echo $(MYVAR) # Output: abc 2. Pass Variables Across Different Jobs
Use isOutput=true and reference the variable via dependencies.
Example:
jobs:
- job: Job1
steps:
- bash: |
echo "##vso[task.setvariable variable=MYVAR;isOutput=true]abc"
name: SetVarStep
- job: Job2
dependsOn: Job1
variables:
MYVAR: $[ dependencies.Job1.outputs['SetVarStep.MYVAR'] ]
steps:
- script: echo $(MYVAR) # Output: abc 3. Pass variables Across Different Stages
Use stageDependencies to pass variables between stages.
Example:
stages:
- stage: StageA
jobs:
- job: JobA
steps:
- bash: |
echo "##vso[task.setvariable variable=MYVAR;isOutput=true]abc"
name: SetVarStep
- stage: StageB
dependsOn: StageA
variables:
MYVAR: $[ stageDependencies.StageA.JobA.outputs['SetVarStep.MYVAR'] ]
jobs:
- job: JobB
steps:
- script: echo $(MYVAR) # Output: abc Triggers in Azure DevOps yml pipeline
Triggers automate pipeline execution based on events like code commits or pull requests.
1. CI Trigger in yaml pipeline (Continuous Integration)
Runs when changes are pushed to specified branches/paths.
Example:
trigger:
branches:
include:
- main
paths:
include:
- src/**
exclude:
- src/docs/** 2. PR Trigger (Pull Request Validation)
Runs when a PR is created or updated.
Example:
pr:
branches:
include:
- main
paths:
include:
- src/** 🔹 Best Practices for Triggers:
- Use
batch: trueto optimize CI runs. - Path filters are case-sensitive.
- Wildcards (
*) are not supported in path filters.
Conditions in Pipelines
Control when jobs, steps, or stages run using conditions.
Common Conditions in Azure DevOps yml pipeline:
| Condition | Description |
|---|---|
succeeded() | Runs only if previous steps succeeded (default). |
always() | Runs regardless of previous failures. |
failed() | Runs only if previous steps failed. |
eq(variables['var'], 'value') | Runs if a variable matches a value. |
Example:
steps:
- script: echo "This runs only on main branch"
condition: eq(variables['Build.SourceBranch'], 'refs/heads/main') FAQ
Q1. What is the difference between a stage, job, and step in Azure DevOps YAML pipeline?
A stage is the highest level in a YAML pipeline — it represents a major phase like Build, Test, or Deploy. A job is a collection of steps that run together on the same agent. A step is the smallest unit — a single script or task like running npm install or dotnet build. Think of it as: pipeline → stages → jobs → steps.
Q2. Can jobs run in parallel in an Azure DevOps YAML pipeline?
Yes. By default, jobs within the same stage run in parallel if you have available agents. To run them sequentially, use dependsOn to define the order — for example, Job2 can be set to run only after Job1 completes successfully.
Q3. How do I pass a variable from one stage to another in Azure DevOps YAML?
Use isOutput=true when setting the variable in the source stage, then reference it in the target stage using stageDependencies. The target stage must also declare dependsOn pointing to the source stage, otherwise the variable reference will fail silently.
Q4. Why is my Azure DevOps YAML pipeline CI trigger not working?
The most common reasons are: the branch name in your trigger block doesn't exactly match your repository branch (including case sensitivity), path filters are misconfigured, or the pipeline was manually set to override triggers in the UI. Also check that trigger: none isn't accidentally set at the top of your YAML file.
Q5. What is the difference between a CI trigger and a PR trigger in Azure DevOps?
A CI trigger (trigger:) fires when code is pushed or merged into a branch. A PR trigger (pr:) fires when a pull request is created or updated against a target branch. CI triggers are used to deploy or build after merging, while PR triggers are used to validate code before merging.
Q6. What does the succeeded() condition do in Azure DevOps YAML?
succeeded() is the default condition applied to every step, job, or stage. It means the task only runs if all previous tasks completed without errors. You can override this with always() to run regardless of failures, or failed() to run a cleanup step only when something goes wrong.
Q7. Can I reuse YAML pipeline code across multiple pipelines in Azure DevOps?
Yes — Azure DevOps supports YAML templates, which let you define reusable stages, jobs, or steps in a separate .yml file and reference them from multiple pipelines using the template: keyword. This is one of the most powerful features for keeping large pipeline codebases maintainable.
