Getting Started with Azure DevOps Pipelines: From Hello World to Real-World Deployments¶
Learn how to build and deploy Azure DevOps Pipelines, from a simple "Hello World" to real-world multi-job deployments.
π Hello-World Pipeline¶
- This is your first Azure DevOps Pipelineβthe classic "Hello World!" example.
- You'll see how to trigger a pipeline, choose an agent, and print a message.
Azure DevOps YAML Pipeline to Print "Hello World"
# Basic Azure DevOps Pipeline YAML to print "Hello World"
# 'trigger' defines which branch will trigger the pipeline automatically.
trigger:
- main # (1)!
# 'pool' specifies the agent (virtual machine) where the pipeline will run.
pool:
vmImage: ubuntu-latest # (2)!
# 'steps' define the tasks the pipeline will execute.
steps:
- script: echo "Hello World from Azure DevOps!" # (3)!
displayName: 'Print Hello World'
-
trigger β Start the Pipeline Automatically. The trigger field specifies the branch that kicks off the pipeline.
-
main
-
development
-
feature1
-
feature2
-
-
pool β Choose an Agent. The pool defines where the pipeline runs.
-
ubuntu-latest uses a ready-to-go Ubuntu machine provided by Microsoft.
-
windows-latest
-
macos-latest
-
-
steps β Define the Tasks. steps list the actions the agent should perform.
-
script: echo "Hello World..." simply runs a command to print a message in the pipeline logs.
-
script: run main.py #Run Python Script
-
script: Any other "Commands Here"
-
π Summary:
Concept | Description |
---|---|
trigger | Tells when to run (based on branch). |
pool | Tells where to run (which agent image). |
steps | Tells what to run (the actual commands). |
π§© Variables¶
- Variables in Azure DevOps Pipelines help you store and reuse values like messages, secrets, or configuration settings.
- You can define variables inline in your YAML, in external files, in variable groups, or from Azure Key Vault.
- Variables make your pipelines more flexible, maintainable, and secure.
Inline variables inside YAML
# Azure DevOps Pipeline to print Hello World using variables
trigger:
- main
variables:
greeting: "Hello World from Azure DevOps with Variables!"
pool:
vmImage: ubuntu-latest
steps:
- script: echo $(greeting) #Calling Variable
displayName: 'Print Hello World using Variable'
Using External Variables in Azure DevOps Pipeline
Earlier, we learned how to define variables inside the YAML file itself.
Now, let's take the next step and separate variables cleanly.
-
Version 1: Using a file to store variables.
-
Version 2: Using a Variable Group from Azure DevOps Library.
-
Version 3: Using a Azure Key Vault.
Loading Variable from a File (for example, variables/vars.txt) and use it inside our pipeline.
- Create a file in your Repo in this location
variables/vars.txt
mkdir -p variables/vars.txt
- Paste the below content
variables:
greeting: "Hello World from Template!"
- Create the Main Pipeline YAML
trigger:
- main
# Load variables from an external YAML file
variables:
- template: variables/vars.yml # Load greeting variable from variables/vars.yml
pool:
vmImage: ubuntu-latest
steps:
- script: echo $(greeting)
displayName: 'Print Hello World from Template Variable'
This time, we will create a Variable Group from the Azure DevOps UI and use it inside the pipeline.
-
Create a Variable Group
-
Go to Azure DevOps Portal β Pipelines β Library β + Variable group
- Create a group named (for example):
hello-world-vars
- Add a variable:
- Name: greeting
- Value: Hello World from Variable Group!
β Save it.
- Create the Main Pipeline YAML
trigger:
- main
# Load variables from Azure DevOps Variable Groups
variables:
- group: hello-world-vars
pool:
vmImage: ubuntu-latest
steps:
- script: echo $(greeting)
displayName: 'Print Hello World from Variable Group'
Create a Variable Group Linked to Azure Key Vault.
β Prerequisite | Description |
---|---|
Azure Key Vault | A Key Vault must be created in your Azure subscription. |
Secret Name | greeting |
Secret Value | Hello World from Key Vault! |
Access Configuration | The Azure DevOps service connection must have Get permissions on secrets in the Key Vault. |
- Go to Azure DevOps β Pipelines β Library
- Click + Variable group
- Name it:
hello-world-kv-vars
- Enable Link secrets from an Azure Key Vault as variables
- Select your Azure subscription and the Key Vault name
- Select the secret
greeting
- Save the variable group
trigger:
- main
# Load variables from the Key Vault-linked Variable Group
variables:
- group: hello-world-kv-vars
pool:
vmImage: ubuntu-latest
steps:
- script: echo $(greeting)
displayName: 'Print Hello World from Key Vault'
π·οΈ Pre-defined Variables¶
Pre-defined variables in Azure DevOps are automatically available during pipeline execution. These variables provide useful information about the pipeline's execution environment, build system, and process details. These variables can be used in pipeline definitions to access dynamic data, such as paths, build numbers, and agent details.
Working Directory Variables
Variable Name | Description | Example Value |
---|---|---|
$(Build.SourcesDirectory) |
The directory where the source code is checked out. | /home/vsts/work/1/s |
$(Build.ArtifactStagingDirectory) |
The directory where build artifacts are staged before being published. | /home/vsts/work/1/a |
$(Build.BinariesDirectory) |
The directory where build output (binaries) are stored. | /home/vsts/work/1/b |
$(Build.DropDirectory) |
The directory for the final drop of artifacts or outputs. | /home/vsts/work/1/d |
Artifact Variables
Variable Name | Description | Example Value |
---|---|---|
$(Build.ArtifactStagingDirectory) |
The directory where the build artifacts are staged before being published. | /home/vsts/work/1/a |
$(Build.ArtifactName) |
The name of the artifact being published. | myArtifact.zip |
$(Build.BuildId) |
A unique ID of the current build. | 12345 |
$(Build.DefinitionName) |
The name of the build definition. | BuildPipeline |
Agent/System Variables
Variable Name | Description | Example Value |
---|---|---|
$(Agent.HomeDirectory) |
The home directory of the agent on the machine. | /home/vsts/agent |
$(Agent.OS) |
The operating system of the agent. | Linux |
$(Agent.WorkFolder) |
The directory used by the agent to store temporary files. | /home/vsts/work |
$(Agent.TempDirectory) |
The temporary directory for the agent during builds. | /home/vsts/temp |
$(Pipeline.Workspace) |
The workspace directory where pipeline data is stored. | /home/vsts/work/1/s |
$(Pipeline.BuildId) |
The unique identifier of the current build. | 12345 |
Build and Release Variables
Variable Name | Description | Example Value |
---|---|---|
$(Build.BuildId) |
A unique ID for the current build. | 12345 |
$(Build.DefinitionName) |
The name of the build pipeline definition. | CI-CD Pipeline |
$(Build.SourceBranch) |
The source branch for the current build. | refs/heads/main |
$(Build.Repository.Name) |
The name of the repository associated with the build. | MyRepo |
$(Release.ReleaseId) |
The unique identifier of the release. | 98765 |
$(Release.EnvironmentName) |
The name of the environment in the release pipeline. | Production |
Note
For more details on available variables in Azure DevOps, please refer to the official Microsoft documentation.
Agent Workspace Path: /home/vsts/work/s/a
Breakdown
Full Path Component | Description | Example Value |
---|---|---|
/home/vsts/ |
The base directory for the agent. | /home/vsts/ |
/work/ |
The working directory where the agent works. | /home/vsts/work/ |
/1/ |
A unique number identifying a specific build. | /home/vsts/work/1/ |
/s/ |
The source code directory where your repository is checked out. | /home/vsts/work/1/s/ |
Sample Pipeline
trigger:
- main
pool:
vmImage: ubuntu-latest
steps:
- checkout: self # Checkout the code from the main branch of the Azure Repo
- script: |
echo "Listing files in $(Build.SourcesDirectory):"
ls $(Build.SourcesDirectory) # List all files in the source directory
displayName: 'List Files from Repo'
π Environment¶
An Environment in Azure DevOps represents a target locationβsuch as Dev, QA, or Prodβwhere your application is deployed.
Environments help you manage deployments, set up approvals and checks, track deployment history, and control access for each stage of your release pipeline.
Creating Environment
- Go to Azure DevOps β Pipelines β Environments.
- Create a New Environment (example: Dev-Environment).
- Inside that Environment, go to Approvals and Checks.
- Add an Approval β Example: Add yourself or your team to approve.
β‘ Super Simple Flow:
Code Push β Pipeline Trigger β Environment Approval Needed β Approver Clicks "Approve" β Pipeline Continues
trigger:
- main
variables:
- template: variables/vars.yml # Load greeting variable from variables/vars.yml
pool:
vmImage: ubuntu-latest
# Link this pipeline to an Environment
environment: Dev-Environment # Specify the environment name
# Define the steps
steps:
- script: echo $(greeting)
displayName: 'Print Hello World from Template Variable'
Info
β You can attach your pipeline to an Environment, and then configure approvals (like someone must manually approve) before the pipeline can proceed.
π§± Jobs¶
- A job is a collection of steps that run together on the same agent.
- Each job gets its own clean machine (agent).
- If you have multiple jobs, they can run parallel or sequential based on how you design.
- Jobs help organize tasks like "Build", "Test", "Deploy" into separate logical blocks.
β‘ Super Simple Flow:
Job Start β Step 1 β Step 2 β Step "n" β Job End
trigger:
- main
variables:
- template: variables/vars.yml # Load greeting variable from external file
pool:
vmImage: ubuntu-latest
jobs:
- job: PrintJob # Define a Job named 'PrintJob'
displayName: 'Print Greeting Job'
environment: Dev-Environment # Attach Environment directly to Job β
steps: # Steps inside the Job
- script: echo $(greeting)
displayName: 'Print Hello World from Template Variable'
trigger:
- main
variables:
- template: variables/vars.yml # Load variables from external template
pool:
vmImage: ubuntu-latest
jobs:
- job: BuildJob
displayName: 'Build Job'
environment: Dev-Environment # Attach Environment directly to the Build job
steps:
- script: |
echo $(greeting) # Use the greeting variable from the template
echo "Building the project..."
displayName: 'Run Build Steps'
- job: DeployJob
displayName: 'Deploy Job'
dependsOn: BuildJob # This job depends on 'BuildJob'
environment: Dev-Environment # Attach the same environment to Deploy job (can be different too)
steps:
- script: |
echo $(greeting) # Use the greeting variable again
echo "Deploying the project..."
displayName: 'Run Deployment Steps'
Info
If you have only a single dependency (example: dependsOn a single job like dependsOn: BuildJob), you can skip square brackets and just write it directly without [ ].
trigger:
- main
variables:
- template: variables/vars.yml # Load variables from external template
pool:
vmImage: ubuntu-latest
jobs:
- job: BuildJob
displayName: 'Build Job'
environment: Dev-Environment # Attach Environment directly to the Build job
steps:
- script: |
echo $(greeting) # Use the greeting variable from the template
echo "Building the project..."
displayName: 'Run Build Steps'
- job: TestJob
displayName: 'Test Job'
dependsOn: BuildJob # This job depends on 'BuildJob'
environment: Test-Environment # Attach a different environment to the Test job
steps:
- script: |
echo $(greeting) # Use the greeting variable again
echo "Running tests..."
displayName: 'Run Test Steps'
- job: DeployJob
displayName: 'Deploy Job'
dependsOn: [BuildJob, TestJob] # This job depends on both 'BuildJob' and 'TestJob'
environment: Test-Environment # Attach the Test environment to Deploy job
steps:
- script: |
echo $(greeting) # Use the greeting variable again
echo "Deploying the project..."
displayName: 'Run Deployment Steps'
Info
When you have multiple dependencies (example: dependsOn multiple jobs like dependsOn: [BuildJob, TestJob]), you must use square brackets [ ] to list them.
π Summary:
Concept | Description |
---|---|
Steps | Small tasks (scripts, copy files, etc.) |
Job | A collection of steps |
Environment | Can be attached at the Job level too if needed |
dependsOn Usage | Use [ ] when depending on multiple jobs, no [ ] needed for single job |
π¦ Stages¶
A stage in Azure DevOps is like a big chapter inside your pipeline.
Each stage groups related steps together.
Example:
- One stage for building your code (Build
stage)
- Another stage for deploying your app (Deploy
stage)
β
Stages help you organize your pipeline.
β
You can also control approvals, conditions, and flows between stages.
In Simple Terms
If Build succeeds β then go to Deploy after approval.
β‘ Super Simple Flow:
Trigger β Build Stage β Deploy Stage (Approval) β Done
trigger:
- main
# Load variables from external file
variables:
- template: variables/vars.yml
# Use Ubuntu agent
pool:
vmImage: ubuntu-latest
# Define stages
stages:
- stage: Build
displayName: 'Build Stage'
jobs:
- job: BuildJob
displayName: 'Run Build Job'
steps:
- script: echo "Building project..."
displayName: 'Build Step'
- script: echo $(greeting)
displayName: 'Print Greeting Message'
- stage: Deploy
displayName: 'Deploy Stage'
dependsOn: Build # wait for build to complete
jobs:
- job: DeployJob
displayName: 'Run Deploy Job'
environment: Dev-Environment # Add Approval Here β
steps:
- script: echo "Deploying to server..."
displayName: 'Deployment Step'
π Summary:
Concept | Description |
---|---|
Steps | Small tasks (scripts, copy files, etc.) |
Job | A collection of steps |
Stages | A collection of jobs, representing a larger unit of work |
Environment | Can be attached at the Job level too if needed |
π§© Templates¶
Templates in Azure DevOps Pipelines allow you to reuse pipeline logic just like functions or modules in code. You can abstract steps
, jobs
, or stages
into separate files and call them in multiple pipelines or even from other repositories.π
Why Use Templates?
- Follow DRY (Don't Repeat Yourself) principles
- Promote consistency across pipelines
- Centralized maintenance of reusable logic
β‘ Super Simple Flow:
Code Push β Pipeline Trigger
β Variables Loaded from Template (vars.yml)
β Shared Job Template Executes Build
β Environment Approval Needed
β Approver Clicks "Approve"
β Deployment Job from Template Executes
β Pipeline Complete
Template File: templates/print-greeting.yml
steps:
- script: echo $(greeting)
displayName: 'Print Greeting from Template'
Main Pipeline: azure-pipelines.yml
trigger:
- main
variables:
- template: variables/vars.yml # External variable template
pool:
vmImage: ubuntu-latest
steps:
- template: templates/print-greeting.yml
Template File: templates/job-template.yml
parameters:
- name: jobName
type: string
- name: environmentName
type: string
- name: message
type: string
jobs:
- job: ${{ parameters.jobName }}
displayName: "Job: ${{ parameters.jobName }}"
environment: ${{ parameters.environmentName }}
pool:
vmImage: ubuntu-latest
steps:
- script: echo "${{ parameters.message }}"
displayName: "Run Common Task"
Main Pipeline: azure-pipelines.yml
trigger:
- main
variables:
- template: variables/vars.yml
jobs:
- template: templates/job-template.yml
parameters:
jobName: BuildJob
environmentName: Dev
message: "Running build template"
- template: templates/job-template.yml
parameters:
jobName: TestJob
environmentName: QA
message: "Running tests via template"
- template: templates/job-template.yml
parameters:
jobName: DeployJob
environmentName: Prod
message: "Deploying using shared template"
To use a template from another repository:
- Declare the external repo in
resources
:
resources:
repositories:
- repository: sharedtemplates
type: git
name: your-org/shared-template-repo
- Checkout both repos:
steps:
- checkout: self
- checkout: sharedtemplates
- Reference the external template using
@repoAlias
:
- template: templates/deploy.yml@sharedtemplates
trigger:
- main
resources:
repositories:
- repository: sharedtemplates
type: git
name: your-org/shared-template-repo
variables:
- template: variables/vars.yml
steps:
- checkout: self
- checkout: sharedtemplates
- template: templates/deploy.yml@sharedtemplates # using template from the sharedtemplates repo
parameters:
environment: 'dev'
message: 'Deploying from external shared template'
π Summary:
Concept | Description |
---|---|
Step Template | Reuse grouped steps like echo or build tasks |
Job Template | Share common job logic with parameters |
Parameters | Use type: string for clarity in templates |
Variable Template | Include shared variables with - template: variables/vars.yml |
Cross-Repo Usage | Use resources , checkout , and @repo to import external logic |
π Best Practices¶
- Use templates for all shared logic.
- Keep secrets out of source control.
- Use environments and approvals for production deployments.
- Keep pipelines DRY and modular.
- Use descriptive names for jobs, stages, and variables.
π οΈ Troubleshooting¶
-
Pipeline fails to trigger?
Check yourtrigger
branch name matches your repo. -
Variable not found?
Ensure variable names are correct and referenced with$(variableName)
. -
Approval not working?
Confirm youβve set up the environment and assigned approvers in Azure DevOps.
β FAQ¶
Q: Can I run pipelines on my own machine?
A: Yes, by setting up a self-hosted agent.
Q: How do I secure secrets?
A: Use Azure Key Vault and variable groups, never hard-code secrets in YAML.
Q: Can I use templates across repositories?
A: Yes, use the resources
and @repo
syntax as shown above.