A team can automate builds and still deploy the wrong package. A release job rebuilds the application, picks up a newer dependency, and sends production something different from the version tested earlier. Meanwhile, a shared deployment credential sits in a variable nobody owns.
These Azure DevOps YAML examples address problems like those: controlled triggers, repeatable builds, useful test results, secure authentication, and artifact-based deployment.
You will learn where each snippet belongs, what it assumes, and how to adapt it without turning a working demonstration into an unsafe production pipeline.
Before Using These Azure DevOps YAML Examples
Azure Pipelines reads YAML to define automation. A stage groups major phases, a job runs work on an agent, and steps execute commands or tasks inside that job.
The examples target Azure DevOps Services. Unless labeled as complete, snippets are fragments to insert into your existing pipeline—not fifteen files to concatenate.
Before starting:
- Create a test repository and pipeline.
- Confirm agent access and available parallel-job capacity.
- Replace placeholder resource names and service connections.
- Create referenced variable groups, environments, and secrets.
- Test deployment examples against non-production resources.
Microsoft-hosted agents provide disposable execution environments. Self-hosted agents offer network control but require patching, isolation, and maintenance. Review the Azure DevOps beginner tutorial if these concepts are new.
Pro Tip: I recommend proving one small build before adding deployment permissions. That separates application problems from authentication and infrastructure problems.
Examples 1–3: Control When Pipelines Run
1. Build Main-Branch Changes Only
This complete starter pipeline runs after changes under src or to the pipeline file.
trigger:
branches:
include:
- main
paths:
include:
- src/**
- azure-pipelines.yml
pool:
vmImage: ubuntu-24.04
steps:
- checkout: self
- bash: echo "Source validation started."
displayName: Validate checkoutBranch and path filters reduce unnecessary runs. However, include shared build scripts and dependency files when they affect compilation. Otherwise, important changes may escape validation.
The image label identifies an agent operating system, not a permanently frozen toolchain.
2. Validate Pull Requests Appropriately
For supported GitHub or Bitbucket Cloud repositories, use this root-level fragment:
pr:
branches:
include:
- main
paths:
include:
- src/**
- package.json
- package-lock.jsonFor Azure Repos Git, configure a build-validation branch policy instead; YAML pr triggers do not provide that workflow.
Keep production connections and privileged self-hosted agents away from untrusted pull-request code. A test command can execute arbitrary repository code.
3. Schedule Nightly Validation
Add this at the root of a pipeline whose existing steps perform validation:
schedules:
- cron: "0 2 * * 1-5"
displayName: Weekday validation
branches:
include:
- main
always: trueThe schedule uses UTC. always: true requests scheduled runs even without source changes, which suits dependency or integration checks.
Check for schedules configured through the interface, since they can affect YAML scheduling behavior. Avoid nightly production deployments merely because a build schedule exists.
Pro Tip: I recommend assigning an owner to scheduled pipelines. A forgotten nightly job can consume agent capacity and keep temporary Azure resources running.
Examples 4–6: Make Configuration Explicit
4. Define Nonsecret Variables
Place variables at pipeline root and use them in steps:
variables:
buildConfiguration: Release
nodeVersion: 22.x
steps:
- bash: echo "Configuration is $(buildConfiguration)"
displayName: Show build configurationThe $(name) macro expands during pipeline execution. Use variables for ordinary configuration, not passwords committed to YAML.
Centralizing values avoids inconsistent settings across jobs. See Azure pipeline YAML variables for more patterns.
5. Restrict Runtime Parameter Choices
This fragment chooses a permitted target configuration when someone queues a run:
parameters:
- name: target
type: string
default: dev
values:
- dev
- test
steps:
- bash: echo "Selected target is ${{ parameters.target }}"
displayName: Show selected targetParameters use template expressions and are resolved during template expansion. They are not secret storage.
An allowed-value list prevents accidental spelling variations, but it does not authorize deployment. Resource permissions still control what a pipeline may access.
6. Reuse an Authorized Variable Group
Create and authorize orders-dev-settings first:
variables:
- group: orders-dev-settings
steps:
- bash: test -n "$APP_NAME"
displayName: Validate required settings
env:
APP_NAME: $(appName)This reads the group’s appName value without printing it.
Authorize only pipelines that need the group. For secrets, mark values appropriately or use a Key Vault integration; never assume a variable group is secure simply because it is outside the repository.
Pro Tip: I recommend keeping environment settings separate from build logic. The pipeline should change less often than resource names and deployment configuration.
Examples 7–9: Build and Test Reliably
7. Install Node.js and Build Deterministically
These steps assume package.json, a committed lockfile, and a build script:
steps:
- task: UseNode@1
inputs:
version: $(nodeVersion)
- bash: |
set -euo pipefail
npm ci
npm run build
displayName: Install and buildnpm ci installs from the lockfile and fails when it conflicts with package.json. It is preferable to silently changing dependency resolution during a release build.
Select a Node.js version supported by your application and hosting platform.
8. Cache Downloads, Not Installed Dependencies
Insert the cache step before npm ci:
steps:
- task: Cache@2
inputs:
key: 'npm | "$(Agent.OS)" | package-lock.json'
restoreKeys: |
npm | "$(Agent.OS)"
path: $(Pipeline.Workspace)/.npm
- bash: npm ci
displayName: Install dependencies
env:
npm_config_cache: $(Pipeline.Workspace)/.npmThe lockfile contributes to the cache key. This caches npm downloads, while npm ci still reconstructs installed dependencies.
Caches improve speed; they should not become the source of truth. Never cache credentials or use a shared cache as a tenant-security boundary.
9. Publish Test Results Even After Failure
The application’s test script must generate JUnit XML under test-results.
steps:
- bash: npm test
displayName: Run tests
- task: PublishTestResults@2
condition: succeededOrFailed()
inputs:
testResultsFormat: JUnit
testResultsFiles: '**/test-results/*.xml'
failTaskOnFailedTests: true
failTaskOnMissingResultsFile: trueConfigure the test runner’s reporter explicitly; ordinary console output does not create a JUnit file.
The publication condition preserves diagnostics after test failure. It does not turn failed tests into a successful build.
Pro Tip: I recommend deliberately breaking one test while validating the pipeline. Confirm both the failed status and the visible test report before trusting the quality gate.
Examples 10–12: Promote Outputs and Reuse Logic
10. Publish a Build Artifact
After a successful build, publish the generated static-site directory:
steps:
- publish: $(Build.SourcesDirectory)/dist
artifact: web-dist
displayName: Publish built siteAn artifact is a retained build output that other jobs can download. The folder must exist.
Exclude .env files, credentials, development data, and unrelated repository content. Pipeline artifacts are supported in Azure DevOps Services; on-premises Azure DevOps Server requires a compatible alternative.
11. Download That Same Artifact
Use this in a downstream job or stage:
steps:
- checkout: none
- download: current
artifact: web-dist
- bash: |
test -d "$(Pipeline.Workspace)/web-dist"
echo "Artifact is available."
displayName: Verify downloaded artifactcurrent means the current pipeline run. checkout: none avoids an unnecessary repository checkout.
Downloading rather than rebuilding preserves the tested output. For server-side applications, package the correct deployable files rather than assuming a static dist folder is sufficient.
Create templates/build.yml:
parameters:
- name: runTests
type: boolean
default: true
steps:
- bash: npm ci
- bash: npm run build
- ${{ if eq(parameters.runTests, true) }}:
- bash: npm testReference it from the pipeline:
steps:
- template: templates/build.yml
parameters:
runTests: trueTemplates reduce copy-and-paste drift. Protect shared templates with code review because a change can affect many applications.
Do not expose a “skip tests” option in a protected release workflow without a deliberate governance decision. Review Azure DevOps best practices for team-wide controls.
Pro Tip: I recommend promoting artifacts through environments, not source branches rebuilt independently. Record the run ID so support can identify exactly what was released.
Examples 13–15: Secure Azure Access and Deployment
13. Retrieve a Secret From Key Vault
Create a narrowly scoped Azure Resource Manager service connection named sc-orders-dev using workload identity federation. This authenticates through Microsoft Entra ID without a stored client secret. Grant only required Azure RBAC permissions and authorize the intended pipeline.
steps:
- task: AzureKeyVault@2
inputs:
azureSubscription: sc-orders-dev
KeyVaultName: '<key-vault-name>'
SecretsFilter: orders-api-token
RunAsPreJob: falseThe task exposes the requested secret to subsequent tasks. Map it into an environment variable only where needed, and never echo it.
The identity needs secret-read access. A private vault also requires agent network reachability and DNS; RBAC alone cannot overcome a blocked network path. Read how Azure Key Vault works.
14. Preview Bicep Infrastructure Changes
This step assumes a checked-out Bicep template and parameter file:
steps:
- task: AzureCLI@2
displayName: Preview infrastructure changes
inputs:
azureSubscription: sc-orders-dev
scriptType: bash
scriptLocation: inlineScript
inlineScript: |
set -euo pipefail
az deployment group what-if \
--resource-group "<resource-group-name>" \
--template-file infra/main.bicep \
--parameters infra/dev.bicepparamwhat-if previews proposed changes through Azure Resource Manager. It is not a guarantee of a successful deployment and should not be described as a zero-permission operation.
Review replacements, deletions, and cost increases before approving an actual deployment. Keep parameters environment-specific and secrets out of committe