Share via


Use YAML templates in pipelines for reusable and secure processes

Azure DevOps Server 2019

Use templates to define your logic once and then reuse it several times. Templates combine the content of multiple YAML files into a single pipeline. You can pass parameters into a template from your parent pipeline.

Extend from a template

Insert a template

Extend from a template and use an include template with variables

One common scenario is to have a pipeline with stages for development, testing, and production that uses both an includes template for variables and an extends template for stages and jobs.

In the following example, variables-template.yml defines a set of virtual machine variables that are then used in azure-pipeline.yml.

# variables-template.yml

variables:
- name: devVmImage
  value: 'ubuntu-latest'
- name: testVmImage
  value: 'ubuntu-latest'
- name: prodVmImage
  value: 'ubuntu-latest'

The following file, stage-template.yml defines a reusable stage configuration with three parameters (name, vmImage, steps) and a job named Build.

# stage-template.yml
parameters:
- name: name
  type: string
  default: ''
- name: vmImage
  type: string
  default: ''
- name: steps
  type: stepList
  default: []

stages:
- stage: ${{ parameters.name }}
  jobs:
  - job: Build
    pool:
      vmImage: ${{ parameters.vmImage }}
    steps: ${{ parameters.steps }}

The following pipeline, azure-pipelines.yml, imports variables from variables-template.yml, and then uses the stage-template.yml template for each stage. Each stage (Dev, Test, Prod) is defined with the same template but with different parameters, leading to consistency across stages while allowing for customization. The 'Prod' stage includes an environment variable as an example of something you might use for authentication. To learn more about defining parameters, see Template parameters.

# azure-pipelines.yml
trigger:
- main

variables:
- template: variables-template.yml

stages:
- template: stage-template.yml
  parameters:
    name: Dev
    vmImage: ${{ variables.devVmImage }}
    steps:
      - script: echo "Building in Dev"
- template: stage-template.yml
  parameters:
    name: Test
    vmImage: ${{ variables.testVmImage }}
    steps:
      - script: echo "Testing in Test"
- template: stage-template.yml
  parameters:
    name: Prod
    vmImage: ${{ variables.prodVmImage }}
    steps:
      - script: echo "Deploying to Prod"
        env:
          SYSTEM_ACCESSTOKEN: $(System.AccessToken)

Reference template paths

Template paths can be an absolute path within the repository or relative to the file that does the including.

To use an absolute path, the template path must start with a /. All other paths are considered relative.

Here's an example nested hierarchy.

|
+-- fileA.yml
|
+-- dir1/
     |
     +-- fileB.yml
     |
     +-- dir2/
          |
          +-- fileC.yml

Then, in fileA.yml you can reference fileB.yml and fileC.yml like this.

steps:
- template: dir1/fileB.yml
- template: dir1/dir2/fileC.yml

If fileC.yml is your starting point, you can include fileA.yml and fileB.yml like this.

steps:
- template: ../../fileA.yml
- template: ../fileB.yml

When fileB.yml is your starting point, you can include fileA.yml and fileC.yml like this.

steps:
- template: ../fileA.yml
- template: dir2/fileC.yml

Alternatively, fileB.yml could refer to fileA.yml and fileC.yml using absolute paths like this.

steps:
- template: /fileA.yml
- template: /dir1/dir2/fileC.yml

Store templates in other repositories

You can keep your templates in other repositories. For example, suppose you have a core pipeline that you want all of your app pipelines to use. You can put the template in a core repo and then refer to it from each of your app repos:

# Repo: Contoso/BuildTemplates
# File: common.yml
parameters:
- name: 'vmImage'
  default: 'ubuntu-22.04'
  type: string

jobs:
- job: Build
  pool:
    vmImage: ${{ parameters.vmImage }}
  steps:
  - script: npm install
  - script: npm test

Now you can reuse this template in multiple pipelines. Use the resources specification to provide the location of the core repo. When you refer to the core repo, use @ and the name you gave it in resources.

# Repo: Contoso/LinuxProduct
# File: azure-pipelines.yml
resources:
  repositories:
    - repository: templates
      type: github
      name: Contoso/BuildTemplates

jobs:
- template: common.yml@templates  # Template reference
# Repo: Contoso/WindowsProduct
# File: azure-pipelines.yml
resources:
  repositories:
    - repository: templates
      type: github
      name: Contoso/BuildTemplates
      ref: refs/tags/v1.0 # optional ref to pin to

jobs:
- template: common.yml@templates  # Template reference
  parameters:
    vmImage: 'windows-latest'

For type: github, name is <identity>/<repo> as in the preceding example. For type: git (Azure Repos), name is <project>/<repo>. If that project is in a separate Azure DevOps organization, you need to configure a service connection of type Azure Repos/Team Foundation Server with access to the project and include that in YAML:

resources:
  repositories:
  - repository: templates
    name: Contoso/BuildTemplates
    endpoint: myServiceConnection # Azure DevOps service connection
jobs:
- template: common.yml@templates

Repositories are resolved only once, when the pipeline starts up. After that, the same resource is used during the pipeline run. Only the template files are used. Once the templates are fully expanded, the final pipeline runs as if it were defined entirely in the source repo. This means that you can't use scripts from the template repo in your pipeline.

If you want to use a particular, fixed version of the template, be sure to pin to a ref. The refs are either branches (refs/heads/<name>) or tags (refs/tags/<name>). If you want to pin a specific commit, first create a tag pointing to that commit, then pin to that tag.

Note

If no ref is specified, the pipeline defaults to using refs/heads/main.

You can also pin to a specific commit in Git with the SHA value for a repository resource. The SHA value is a 40-character checksum hash that uniquely identifies the commit.

resources:
  repositories:
    - repository: templates
      type: git
      name: Contoso/BuildTemplates
      ref: 1234567890abcdef1234567890abcdef12345678

You can also use @self to refer to the repository where the original pipeline was found. This is convenient for use in extends templates if you want to refer back to contents in the extending pipeline's repository. For example:

# Repo: Contoso/Central
# File: template.yml
jobs:
- job: PreBuild
  steps: []

  # Template reference to the repo where this template was
  # included from - consumers of the template are expected
  # to provide a "BuildJobs.yml"
- template: BuildJobs.yml@self

- job: PostBuild
  steps: []
# Repo: Contoso/MyProduct
# File: azure-pipelines.yml
resources:
  repositories:
    - repository: templates
      type: git
      name: Contoso/Central

extends:
  template: template.yml@templates
# Repo: Contoso/MyProduct
# File: BuildJobs.yml
jobs:
- job: Build
  steps: []

FAQ

How can I use variables inside of templates?

There are times when it's useful to set parameters to values based on variables. Parameters are expanded early in processing a pipeline run so not all variables are available. To see what predefined variables are available in templates, see Use predefined variables.

In this example, the predefined variables Build.SourceBranch and Build.Reason are used in conditions in template.yml.

# File: azure-pipelines.yml
trigger:
- main

extends:
  template: template.yml
# File: template.yml
steps:
- script: echo Build.SourceBranch = $(Build.SourceBranch) # outputs refs/heads/main
- script: echo Build.Reason = $(Build.Reason) # outputs IndividualCI
- ${{ if eq(variables['Build.SourceBranch'], 'refs/heads/main') }}: 
  - script: echo I run only if Build.SourceBranch = refs/heads/main 
- ${{ if eq(variables['Build.Reason'], 'IndividualCI') }}: 
  - script: echo I run only if Build.Reason = IndividualCI 
- script: echo I run after the conditions