
Using Template Libraries for Build and Deploy Stages in Azure DevOps Pipelines
Picture this: one client I worked with had a 500-line azure-pipelines.yml
—build here, deploy there, all tangled up. Another had five repos with copied-and-pasted stages—change one variable, fix it everywhere. Sound familiar? It’s a maintenance nightmare. Drift creeps in, errors pile up, and scaling becomes a slog.
Having Git repositories with dedicated and parameterised build and deploy templates in Azure Repos, fixes that.
Stick your templates—build, deploy, whatever—in one place, one Azure Repos git repo. Update once, and reuse them across features and other Azure Repos git repositories.
I’ve run hundreds of stages this way for big names. It’s not just tidy; it’s faster. You’re not rewriting the same deploy logic for dev, test, and prod. Let’s have a look how we can achieve this easily.
How to Make It Work
Here’s the nuts and bolts—three steps to get your build and deploy stages living in an external repo in Azure Repos, that gets pulled in for your multiple feature repo pipelines that need them.
Step 1: Setup the Repo
Create a new Git repo—call it templates-repo
—on Azure Repos. This is your library. Inside, add two files:
build.yml
for your build stage.deploy.yml
for your deploy stage.
Keep it simple to start—I’ll show you examples next.
Step 2: Write the Templates
These are parameterised YAML pipeline files—flexible, reusable. Here’s what they might look like:
build.yml
:
parameters:
- name: buildConfig
default: Release
stages:
- stage: Build
jobs:
- job: BuildJob
steps:
- script: echo Building ${{ parameters.buildConfig }}
displayName: Build Step
- script: |
# Build here and copy output to Build.ArtifactStagingDirectory
echo "Replace this with your build command (e.g., dotnet build)"
cp -r ./output/* $(Build.ArtifactStagingDirectory)/
displayName: Build and Copy Artifacts
- task: PublishBuildArtifacts@1
displayName: Publish artifacts
inputs:
PathtoPublish: $(Build.ArtifactStagingDirectory)
ArtifactName: drop
publishLocation: Container
This builds with a config (e.g., Release or Debug) you pass in. You can extend the steps to your liking.
Next, we have our deploy.yml
template, again parameterised this time with an envName
. You can add as many other parameters as required. And even have separate deploy.yml templates. Think docker-deploy.yml
, dotnet-deploy.yml
etc, you get the idea.
deploy.yml
:
parameters:
- name: envName
default: dev
stages:
- stage: Deploy_${{ parameters.envName }}
jobs:
- deployment: DeployJob
environment: ${{ parameters.envName }}
strategy:
runOnce:
deploy:
steps:
- script: echo Deploying to ${{ parameters.envName }}
displayName: Deploy Step
This deploys to an env (e.g., dev
, prod
) you specify. Parameters make this all portable and repeatable.
Step 3: Reference in Your Pipeline
In your main project repo, tweak azure-pipelines.yml
to pull these in:
resources:
repositories:
- repository: templates
type: git
name: templates-repo # Replace with your repo name
stages:
- template: build.yml@templates
parameters:
buildConfig: Release
- template: deploy.yml@templates
parameters:
envName: dev
- template: deploy.yml@templates
parameters:
envName: prod
The example above highlights how the deploy.yml
template can be reused for the dev
and prod
deploy stage. They both use the same build artifact and deploy this to the relevant environment.
It’s easy to see how the parameters for both the build.yml
and deploy.yml
stage can be tweaked and extended to suit your use case(s).
The resources.repositories
block links your existing repo—templates
git repo in Azure Repos is just an alias. Then @templates
calls the files, passing parameters. Run it—builds in Release, deploys to prod. Swap envName to dev
, and you’re golden. One repo, infinite uses. And it’s easy to extend your templates with sensible defaults.
Why This Wins
I’ve been automating pipelines for 15 years—external templates and thinking about parameterisation and re-usability and adopting your approach to cater for this mechanism is a game changer.
For the World Health Organisation, I created CruiseControl.NET XML templates that were highly parameterised.
And these days, with Azure DevOps, it’s easier than ever. But unless you’ve gone through the pain, it’s hard to appreciate the work required to have solid CI/CD pipeline templates in place that can be reused easily without breaking other pipelines or causing an administrative mess where autonomous teams just copy and paste without thinking.
Months of tinkering saved—dev teams could focus on code, not YAML. Pair it with Bicep for infra, and you’ve got a system—scalable, readable, done.
Stay tuned for more practical tips here and ready-baked templates you can use.