Skip to content

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"

azure-pipelines.yml
# 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'  
  1. trigger β€” Start the Pipeline Automatically. The trigger field specifies the branch that kicks off the pipeline.

    • main

    • development

    • feature1

    • feature2

  2. 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

  3. 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-pipelines.yml
# 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
azure-pipelines.yml
variables:
greeting: "Hello World from Template!"
  • Create the Main Pipeline YAML
azure-pipelines.yml
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
azure-pipelines.yml
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.
  1. Go to Azure DevOps β†’ Pipelines β†’ Library
  2. Click + Variable group
  3. Name it: hello-world-kv-vars
  4. Enable Link secrets from an Azure Key Vault as variables
  5. Select your Azure subscription and the Key Vault name
  6. Select the secret greeting
  7. Save the variable group
azure-pipelines.yml
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

azure-pipelines.yml
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
azure-pipelines.yml
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
azure-pipelines.yml
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'
azure-pipelines.yml
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 [ ].

azure-pipelines.yml
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
azure-pipelines.yml
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

templates/print-greeting.yml
steps:
  - script: echo $(greeting)
    displayName: 'Print Greeting from Template'

Main Pipeline: azure-pipelines.yml

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

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

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
azure-pipelines.yml
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 your trigger 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.


πŸ“š Further Reading