Azure Pipelines Generator
Generates production-ready Azure DevOps Pipelines (azure-pipelines.yml) following current best practices, security standards, and naming conventions. Use when creating or updating ADO YAML pipelines, configuring build triggers, defining multi-stage deployments, setting up template references, creating variable groups, writing release pipelines, or structuring CI/CD workflows for Azure DevOps Services or Azure DevOps Server. Handles build pipelines, YAML pipelines, Docker container builds, Kubernetes/AKS deployments, language-specific pipelines (.NET, Node.js, Python, Go, Java), and reusable step/job/stage templates. All generated configurations are validated using the devops-skills:azure-pipelines-validator skill before delivery.
npx skills add github:pantheon-org/tekhne --skill ci-cd--azure-pipelines--generatorOverview
Generate production-ready Azure DevOps Pipeline configurations following current best practices, security standards, and naming conventions. After generating any complete pipeline file, always validate it using the devops-skills:azure-pipelines-validator skill, fix any reported issues, and re-validate before presenting to the user. Skip validation only for partial snippets, documentation examples, or when the user explicitly requests it.
Philosophy
- Generate pipelines that are deterministic: pin every image, task and runtime version.
- Keep secrets out of YAML; reference service connections, variable groups or Key Vault instead.
- Prefer reusable templates over long inline pipelines, and validate every complete file before delivery.
When to Use
- Creating or updating an
azure-pipelines.ymlfor build, test or deploy. - Defining multi-stage deployments with environments, variable groups and service connections.
- Writing Docker build and Kubernetes/AKS deployment pipelines.
- Producing reusable step, job or stage templates and the pipelines that consume them.
When Not to Use
- Reviewing or linting an existing pipeline without changing it: use the validator skill alone.
- Targeting other CI systems such as GitHub Actions, GitLab CI or Jenkins.
- Classic (UI-based) release pipelines that have no YAML definition.
- Provisioning the Azure DevOps organisation, agents or service connections themselves.
Core Capabilities
1. Basic CI Pipelines
Read references/yaml-schema.md, references/best-practices.md, references/tasks-reference.md, and assets/examples/basic-ci.yml.
Example:
trigger:
branches:
include:
- main
- develop
pool:
vmImage: 'ubuntu-22.04'
variables:
buildConfiguration: 'Release'
steps:
- task: NodeTool@0
displayName: 'Install Node.js'
inputs:
versionSpec: '20.x'
- task: Cache@2
displayName: 'Cache npm packages'
inputs:
key: 'npm | "$(Agent.OS)" | package-lock.json'
path: $(Pipeline.Workspace)/.npm
- script: npm ci --cache $(Pipeline.Workspace)/.npm
displayName: 'Install dependencies'
- script: npm run build
displayName: 'Build application'
- script: npm test
displayName: 'Run tests'
- task: PublishTestResults@2
condition: succeededOrFailed()
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '**/test-results.xml'
2. Multi-Stage CI/CD Pipelines
Read references/yaml-schema.md and assets/examples/multi-stage-cicd.yml. Use deployment jobs for environment tracking; publish artifacts between stages.
Example:
stages:
- stage: Build
displayName: 'Build Stage'
jobs:
- job: BuildJob
displayName: 'Build Application'
pool:
vmImage: 'ubuntu-22.04'
steps:
- script: npm run build
displayName: 'Build'
- publish: $(Build.SourcesDirectory)/dist
artifact: drop
- stage: Test
displayName: 'Test Stage'
dependsOn: Build
jobs:
- job: TestJob
displayName: 'Run Tests'
steps:
- script: npm test
displayName: 'Test'
- stage: DeployProd
displayName: 'Deploy to Production'
dependsOn: Test
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: DeployProd
environment: production
strategy:
runOnce:
deploy:
steps:
- script: echo "Deploying"
3. Docker Build Pipelines
Read references/tasks-reference.md for Docker@2 and assets/examples/kubernetes-deploy.yml. Use service connection for authentication; tag with $(Build.BuildId) as primary.
Example:
variables:
dockerRegistryServiceConnection: 'myACR'
imageRepository: 'myapp'
containerRegistry: 'myregistry.azurecr.io'
tag: '$(Build.BuildId)'
steps:
- task: Docker@2
displayName: 'Build and Push'
inputs:
command: buildAndPush
repository: $(imageRepository)
dockerfile: '$(Build.SourcesDirectory)/Dockerfile'
containerRegistry: $(dockerRegistryServiceConnection)
tags: |
$(tag)
latest
Tagging rule: Push with
$(tag)ANDlatest; deploy/pull using only the specific$(tag)— never:latestin production deployments.
4. Kubernetes Deployment Pipelines
Read references/tasks-reference.md and assets/examples/kubernetes-deploy.yml. Use KubernetesManifest@0 or Kubernetes@1; include namespace management and health checks.
Example:
- task: KubernetesManifest@0
displayName: 'Deploy to Kubernetes'
inputs:
action: 'deploy'
kubernetesServiceConnection: 'myK8sCluster'
namespace: 'production'
manifests: |
k8s/deployment.yml
k8s/service.yml
containers: '$(containerRegistry)/$(imageRepository):$(tag)'
5. Language-Specific Pipelines
Supported languages: .NET/C#, Node.js, Python, Java, Go, Docker multi-stage
Read references/tasks-reference.md and the matching example file:
| Language | Example File |
|---|---|
| Go | assets/examples/go-cicd.yml |
| .NET/C# | assets/examples/dotnet-cicd.yml |
| Python | assets/examples/python-cicd.yml |
| Node.js | assets/examples/basic-ci.yml or multi-stage-cicd.yml |
Include: runtime setup, package manager caching, build, test with reporting, artifact publish.
Go-specific notes:
- Use
GoTool@0(only major version available — @0 is correct) - Cache Go modules at
$(GOPATH)/pkg/modusinggo.sumas key - Run
go vet ./...before tests; use-race -coverprofileflags for test coverage - Build with
CGO_ENABLED=0for container images
Matrix testing pattern:
strategy:
matrix:
node18:
nodeVersion: '18.x'
node20:
nodeVersion: '20.x'
node22:
nodeVersion: '22.x'
maxParallel: 3
steps:
- task: NodeTool@0
inputs:
versionSpec: $(nodeVersion)
- script: npm test
6. Template-Based Pipelines
Read references/templates-guide.md and assets/examples/templates/. Use ${{ parameters.name }} syntax; generate both template and consuming pipeline.
Example:
# templates/build.yml
parameters:
- name: nodeVersion
type: string
default: '20.x'
steps:
- task: NodeTool@0
inputs:
versionSpec: ${{ parameters.nodeVersion }}
- script: npm ci
- script: npm run build
# Main pipeline
steps:
- template: templates/build.yml
parameters:
nodeVersion: '20.x'
7. Task Documentation Lookup
When local docs are sufficient (most cases):
references/tasks-reference.mdcovers .NET, Node.js, Python, Go, Docker, Kubernetes, Azure tasksreferences/yaml-schema.mdcovers complete YAML syntax
When to use external sources (tasks not in local docs, version-specific questions, troubleshooting):
- Context7 MCP (preferred):
mcp__context7__resolve-library-id→ query “azure-pipelines” →mcp__context7__get-library-docs - WebSearch (fallback):
"[TaskName]@[version] Azure Pipelines task documentation"
Analyze retrieved docs for: task name/version, required vs optional inputs, service connection requirements, and outputs.
# Example: task found via documentation lookup
- task: AzureFunctionApp@1
displayName: 'Deploy Azure Function'
inputs:
azureSubscription: 'AzureServiceConnection' # Required: ARM service connection
appType: 'functionAppLinux'
appName: 'myfunctionapp'
package: '$(Build.ArtifactStagingDirectory)/**/*.zip'
runtimeStack: 'NODE|20'
Best Practices to Enforce
Reference references/best-practices.md for comprehensive guidelines.
Mandatory Standards
- Security: Never hardcode secrets; use service connections; mark variables as secret in ADO
- Version pinning:
- vmImage:
ubuntu-22.04notubuntu-latest - Tasks:
Docker@2notDocker(pin to major version; @0 is correct forGoTool@0,NodeTool@0,KubernetesManifest@0) - Runtimes:
'20.x'for Node.js, explicit Go versions
- vmImage:
- Performance: Use
Cache@2for all package managers; usedependsOnfor parallelism; set artifact expiration; shallow clone when full history is unnecessary - Naming conventions:
- Stages/Jobs: PascalCase (
BuildAndTest,DeployProduction) displayName: Sentence case ('Build application','Run tests')- Variables: camelCase or snake_case (be consistent)
- Stages/Jobs: PascalCase (
- Organization: Use stages for complex pipelines; deployment jobs for environment tracking; templates for reusable logic; variable groups for environment-specific config
- Error handling: Set
timeoutInMinutes; usecondition: succeededOrFailed()for test publishing;continueOnErrorfor non-critical steps - Testing: Always publish test results (
PublishTestResults@2) and code coverage (PublishCodeCoverageResults@1)
Workflow Process
For a complete end-to-end workflow example (Understanding → Reading → Lookup → Generate → Validate → Present), see references/typical-workflow.md.
Anti-Patterns
NEVER use latest for task version pins
WHY: ADO task versions introduce breaking changes across major versions. Using @latest or an unpinned reference creates non-deterministic builds where a task update can silently break your pipeline overnight.
BAD: - task: UseNode@latest
GOOD: - task: UseNode@0 with a pinned versionSpec input (e.g., versionSpec: '20.x').
NEVER store secrets in pipeline YAML variables
WHY: YAML variables are committed to source control and visible in pipeline run logs, exposing credentials to anyone with repository read access or pipeline view permissions.
BAD: variables: API_KEY: 'abc123'
GOOD: Use Azure Key Vault task or pipeline variable groups with the “secret” flag enabled in the ADO UI.
NEVER omit displayName: on tasks and steps
WHY: Pipelines without display names produce cryptic logs like Task 1 of 12 that are impossible to interpret when diagnosing a failure, especially in multi-stage pipelines.
BAD: - script: npm ci with no displayName.
GOOD: - script: npm ci followed by displayName: 'Install dependencies'.
NEVER use trigger: none on templates used as main pipelines
WHY: trigger: none disables all automatic triggers, meaning the pipeline never runs on code push. This is appropriate only for templates called by other pipelines, not for CI entry-point pipelines.
BAD: trigger: none on a pipeline intended to run on every commit.
GOOD: Configure explicit branch includes, for example trigger: branches: include: [main, develop].
NEVER define all logic inline in a single flat YAML
WHY: Single-file pipelines exceeding a few hundred lines become unmaintainable, impossible to test in isolation, and prone to merge conflicts when multiple teams update them simultaneously.
BAD: A 400-line azure-pipelines.yml with all stages, jobs, and scripts inlined.
GOOD: Extract stage and job logic into separate templates/*.yml files and reference them with - template: templates/build.yml.
NEVER use ubuntu-latest or other floating vmImage values
WHY: a floating image changes underneath the pipeline, so builds stop being reproducible and can break without any code change.
BAD: vmImage: 'ubuntu-latest'
GOOD: vmImage: 'ubuntu-22.04'
NEVER deploy or pull the latest image tag in production
WHY: latest is mutable, so the running version cannot be traced to a build and rollbacks are ambiguous.
BAD: a Kubernetes manifest deploying $(imageRepository):latest.
GOOD: push with $(tag) and latest, but deploy using only $(tag) (the $(Build.BuildId)).
NEVER use a regular job for an environment deployment
WHY: only deployment jobs give environment tracking, approvals and deployment history.
BAD: a job: that runs kubectl apply for the production stage.
GOOD: a deployment: job with environment: production and a runOnce strategy.
NEVER skip test result publishing or run it only on success
WHY: failed runs are exactly where the test report is needed, and a skipped publish hides which tests failed.
BAD: PublishTestResults@2 with no condition, or no publish step at all.
GOOD: condition: succeededOrFailed() on PublishTestResults@2, plus code coverage publishing.
NEVER deliver a complete pipeline without validating it
WHY: unvalidated YAML ships syntax, hierarchy and security errors straight to the user.
BAD: presenting a generated azure-pipelines.yml straight away.
GOOD: run the azure-pipelines-validator skill, fix reported issues, and re-validate before presenting.
Troubleshooting
If devops-skills:azure-pipelines-validator reports errors
| Error type | Resolution |
|---|---|
| Syntax errors | Fix YAML indentation or structure |
| Task version errors | Ensure format is TaskName@version |
| Pool/vmImage errors | Use specific versions, not latest |
| Stage/Job errors | Verify stages → jobs → steps hierarchy |
| Security warnings | Remove hardcoded secrets; avoid :latest in deployments |
If task documentation is not found
- Try alternative search queries
- Check Microsoft Learn task reference
- Check azure-pipelines-tasks on GitHub
- Ask the user for specific version requirements
References
| Topic | Reference | When to Use |
|---|---|---|
| YAML schema | references/yaml-schema.md | Pipeline structure, triggers, pools, variables, conditions, expressions |
| Task catalogue | references/tasks-reference.md | Task inputs, outputs and service connection requirements |
| Best practices | references/best-practices.md | Security, performance, naming and anti-patterns |
| Templates | references/templates-guide.md | Template types, parameter definitions, expressions, iteration |
| End-to-end workflow | references/typical-workflow.md | A complete worked example including validation steps |
| Basic CI example | assets/examples/basic-ci.yml | Simple CI, single-stage builds |
| Multi-stage example | assets/examples/multi-stage-cicd.yml | Multi-environment deployments |
| Kubernetes example | assets/examples/kubernetes-deploy.yml | Docker plus K8s/AKS deployments |
| Go example | assets/examples/go-cicd.yml | Go applications |
| .NET example | assets/examples/dotnet-cicd.yml | .NET/C# applications |
| Python example | assets/examples/python-cicd.yml | Python applications |
| Template usage example | assets/examples/template-usage.yml | Template-consuming pipelines |
| Build template | assets/examples/templates/build-template.yml | Reusable build templates |
| Deploy template | assets/examples/templates/deploy-template.yml | Reusable deployment templates |
Explicit NEVER/WHY/consequence triplets with side-by-side bad/good examples. Generic warnings score low — concrete failure modes with real impact (security, correctness, performance) score high.
Runtime validation via eval scenarios proving the skill actually changes agent behaviour. Requires evals/ directory, instructions.json, ≥3 scenarios, and ≥80% instruction coverage in summary.json.
Appropriate rigidity for the skill type: strict rules for critical/security skills, balanced steps for process skills, flexible options for tool skills. Mismatched calibration (too rigid or too loose) reduces score.
Expert-only knowledge the AI couldn't derive on its own — domain-specific patterns, production gotchas, decision frameworks, and non-obvious tool behaviour. High scores mean <5% redundancy with what the model already knows.
A clear philosophy plus numbered step-by-step workflows with explicit entry/exit points. Should answer: what is the core principle, how to execute it, and when (and when not) to apply it.
Rich activation keywords and trigger scenarios in the description so the skill fires when needed. Best descriptions read like an exhaustive list of the tasks and concepts that should invoke this skill.
Copy-paste-ready code, complete runnable examples, real file paths and commands. Pseudocode and abstract descriptions score low — concrete, executable content scores high.
SKILL.md as a <100-line navigation hub pointing to focused references/ files. Content should be lazy-loaded on demand, not front-loaded. Includes actionable 'When to Use' conditions per reference.
Proper frontmatter, single-task focus, activation keywords, and cross-harness portability. Skills must work across 40+ agentic harnesses without hardcoded paths or agent-specific references.
Expert-only knowledge the AI couldn't derive on its own — domain-specific patterns, production gotchas, decision frameworks, and non-obvious tool behaviour. High scores mean <5% redundancy with what the model already knows.
A clear philosophy plus numbered step-by-step workflows with explicit entry/exit points. Should answer: what is the core principle, how to execute it, and when (and when not) to apply it.
Explicit NEVER/WHY/consequence triplets with side-by-side bad/good examples. Generic warnings score low — concrete failure modes with real impact (security, correctness, performance) score high.
Proper frontmatter, single-task focus, activation keywords, and cross-harness portability. Skills must work across 40+ agentic harnesses without hardcoded paths or agent-specific references.
SKILL.md as a <100-line navigation hub pointing to focused references/ files. Content should be lazy-loaded on demand, not front-loaded. Includes actionable 'When to Use' conditions per reference.
Appropriate rigidity for the skill type: strict rules for critical/security skills, balanced steps for process skills, flexible options for tool skills. Mismatched calibration (too rigid or too loose) reduces score.
Rich activation keywords and trigger scenarios in the description so the skill fires when needed. Best descriptions read like an exhaustive list of the tasks and concepts that should invoke this skill.
Copy-paste-ready code, complete runnable examples, real file paths and commands. Pseudocode and abstract descriptions score low — concrete, executable content scores high.
No eval scenarios available.
Azure Pipelines Best Practices
This document outlines best practices for creating maintainable, secure, and efficient Azure Pipelines.
Security Best Practices
1. Never Hardcode Secrets
❌ Bad:
variables:
API_KEY: 'sk-1234567890abcdef'
PASSWORD: 'MyP@ssw0rd'
✅ Good:
variables:
- group: 'my-secrets' # From variable group
- name: API_KEY
value: $(SecretApiKey) # From pipeline variables marked as secret
2. Pin Image and Task Versions
❌ Bad:
pool:
vmImage: 'ubuntu-latest'
- task: Docker@2
✅ Good:
pool:
vmImage: 'ubuntu-22.04' # Specific version
- task: Docker@2 # Specific major version
3. Use Service Connections
Store credentials in service connections, not in pipeline variables.
- task: Docker@2
inputs:
containerRegistry: 'myDockerRegistryServiceConnection' # Service connection
command: 'login'
4. Mark Sensitive Variables as Secret
variables:
- name: API_TOKEN
value: $(SecretToken)
# In Azure DevOps UI, mark variable as secret
5. Limit Permissions
Use the principle of least privilege for service connections and agent pools.
Performance Optimization
1. Use Caching
Cache dependencies to speed up builds.
- task: Cache@2
displayName: 'Cache npm packages'
inputs:
key: 'npm | "$(Agent.OS)" | package-lock.json'
restoreKeys: |
npm | "$(Agent.OS)"
path: $(Pipeline.Workspace)/.npm
- script: npm ci --cache $(Pipeline.Workspace)/.npm
displayName: 'Install dependencies'
2. Optimize Dependencies with dependsOn and condition
Use explicit dependencies to run jobs in parallel when possible.
stages:
- stage: Build
jobs:
- job: BuildFrontend
steps:
- script: npm run build:frontend
- job: BuildBackend
steps:
- script: npm run build:backend
- stage: Test
dependsOn: Build
jobs:
- job: TestFrontend
dependsOn: [] # Can start immediately after Build stage
steps:
- script: npm test:frontend
- job: TestBackend
dependsOn: [] # Can start immediately after Build stage
steps:
- script: npm test:backend
3. Use Shallow Clone
Reduce clone time by limiting git history.
steps:
- checkout: self
clean: true
fetchDepth: 1 # Shallow clone
4. Use Artifacts Efficiently
Only publish what’s needed and set expiration.
- task: PublishPipelineArtifact@1
inputs:
targetPath: '$(Build.ArtifactStagingDirectory)/dist' # Only dist folder
artifact: 'webapp'
publishLocation: 'pipeline'
# Set retention in Azure DevOps project settings
5. Use Matrix for Parallel Execution
Test across multiple configurations in parallel.
strategy:
matrix:
node18:
nodeVersion: '18'
node20:
nodeVersion: '20'
node22:
nodeVersion: '22'
maxParallel: 3 # Run 3 at a time
Maintainability
1. Use displayName Everywhere
- stage: Build
displayName: 'Build Application'
jobs:
- job: BuildJob
displayName: 'Build and Compile'
steps:
- script: npm run build
displayName: 'Build with npm'
2. Organize with Stages
Separate concerns into stages for complex pipelines.
stages:
- stage: Build
displayName: 'Build Stage'
jobs: [...]
- stage: Test
displayName: 'Test Stage'
dependsOn: Build
jobs: [...]
- stage: Deploy
displayName: 'Deploy Stage'
dependsOn: Test
jobs: [...]
3. Use Templates for Reusability
Extract common logic into templates.
# templates/npm-build.yml
steps:
- task: NodeTool@0
inputs:
versionSpec: $(nodeVersion)
- task: Cache@2
inputs:
key: 'npm | "$(Agent.OS)" | package-lock.json'
path: $(Pipeline.Workspace)/.npm
- script: npm ci --cache $(Pipeline.Workspace)/.npm
- script: npm run build
# azure-pipelines.yml
steps:
- template: templates/npm-build.yml
parameters:
nodeVersion: '20'
4. Use Variable Groups
Organize variables in variable groups for different environments.
variables:
- group: 'dev-variables'
- group: 'common-variables'
5. Document Your Pipeline
Add comments to explain complex logic.
# This pipeline builds the frontend and backend separately,
# then runs integration tests before deploying to staging.
stages:
- stage: Build
# We build frontend and backend in parallel to save time
jobs:
- job: BuildFrontend
# Frontend uses React and requires Node 20
steps: [...]
Pipeline Structure
1. Naming Conventions
# Stage names: PascalCase
- stage: BuildAndTest
# Job names: PascalCase
- job: BuildApplication
# Step displayNames: Sentence case
- script: echo "test"
displayName: 'Run integration tests'
# Variables: camelCase or snake_case (be consistent)
variables:
buildConfiguration: 'Release'
node_version: '20'
2. Logical Stage Organization
stages:
- stage: Build
jobs: [...]
- stage: UnitTest
dependsOn: Build
jobs: [...]
- stage: IntegrationTest
dependsOn: Build
jobs: [...]
- stage: DeployStaging
dependsOn:
- UnitTest
- IntegrationTest
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop'))
jobs: [...]
- stage: DeployProduction
dependsOn:
- UnitTest
- IntegrationTest
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs: [...]
3. Conditions and Triggers
trigger:
branches:
include:
- main
- develop
- release/*
paths:
exclude:
- docs/**
- README.md
pr:
branches:
include:
- main
paths:
exclude:
- docs/**
Deployment Best Practices
1. Use Deployment Jobs for Environments
- deployment: DeployWeb
displayName: 'Deploy to Production'
pool:
vmImage: 'ubuntu-22.04'
environment:
name: production
resourceName: webapp
strategy:
runOnce:
deploy:
steps:
- script: echo "Deploying"
2. Add Manual Approval for Production
Configure approvals in the environment settings in Azure DevOps.
3. Use Deployment Strategies
# For zero-downtime deployments
strategy:
canary:
increments: [10, 25, 50, 100]
preDeploy:
steps:
- script: echo "Pre-deploy checks"
deploy:
steps:
- script: echo "Deploy to $(strategy.canary.increment)% of instances"
postDeploy:
steps:
- script: echo "Monitor deployment"
4. Implement Rollback Strategy
strategy:
runOnce:
deploy:
steps:
- script: ./deploy.sh
on:
failure:
steps:
- script: ./rollback.sh
displayName: 'Rollback on failure'
Testing
1. Publish Test Results
- script: npm test -- --coverage --ci --reporters=default --reporters=jest-junit
displayName: 'Run tests'
- task: PublishTestResults@2
condition: succeededOrFailed() # Always publish results
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '**/junit.xml'
failTaskOnFailedTests: true
2. Publish Code Coverage
- task: PublishCodeCoverageResults@1
inputs:
codeCoverageTool: 'Cobertura'
summaryFileLocation: '$(System.DefaultWorkingDirectory)/**/coverage/cobertura-coverage.xml'
3. Run Linting and Security Scans
- script: npm run lint
displayName: 'Run ESLint'
- script: npm audit
displayName: 'Security audit'
continueOnError: true # Don't fail build on audit issues
Error Handling
1. Set Timeouts
jobs:
- job: Build
timeoutInMinutes: 30 # Prevent hung jobs
cancelTimeoutInMinutes: 5
2. Use Conditions Appropriately
# Always run cleanup
- script: ./cleanup.sh
displayName: 'Cleanup'
condition: always()
# Only on failure
- script: ./send-alert.sh
displayName: 'Send failure notification'
condition: failed()
# Only on success
- script: ./deploy.sh
displayName: 'Deploy'
condition: succeeded()
3. Use continueOnError for Non-Critical Steps
- script: npm run lint
displayName: 'Run linter'
continueOnError: true # Don't fail the pipeline if linting fails
CI/CD Patterns
1. Multi-Environment Deployment
parameters:
- name: deployToStaging
type: boolean
default: true
- name: deployToProduction
type: boolean
default: false
stages:
- stage: Build
jobs: [...]
- stage: DeployStaging
condition: eq(parameters.deployToStaging, true)
jobs:
- deployment: DeployStaging
environment: staging
- stage: DeployProduction
condition: and(succeeded(), eq(parameters.deployToProduction, true))
dependsOn:
- Build
- DeployStaging
jobs:
- deployment: DeployProduction
environment: production
2. Feature Branch Builds
trigger:
branches:
include:
- main
- feature/*
# Only deploy from main
- stage: Deploy
condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')
jobs: [...]
3. Pull Request Validation
pr:
branches:
include:
- main
paths:
include:
- src/**
stages:
- stage: PRValidation
jobs:
- job: BuildAndTest
steps:
- script: npm install
- script: npm run build
- script: npm test
- script: npm run lint
Common Anti-Patterns to Avoid
❌ Avoid
- Using
latesttags for images or tasks - Hardcoding secrets in pipeline files
- Not using caching for dependencies
- Not publishing test results
- Long-running jobs without timeouts
- Mixing stages/jobs/steps at root level
- Using
@0for task versions (deprecated) - Not using displayName
- Creating monolithic single-stage pipelines for complex workflows
- Not using templates for repeated logic
Summary Checklist
Before committing your pipeline:
- All secrets are in variables/service connections, not hardcoded
- All images and tasks are pinned to specific versions
- displayName is used for all stages, jobs, and complex steps
- Caching is implemented for package managers
- Test results and coverage are published
- Timeout values are set for long-running jobs
- Deployment jobs use environments for tracking
- Templates are used for repeated logic
- Conditions are used to control deployment to production
- Pipeline is validated before committing
Further Reading
Azure Pipelines Tasks Reference
This document provides a reference for commonly used built-in Azure Pipelines tasks.
Task Syntax
- task: TaskName@MajorVersion
displayName: 'Human Readable Name'
inputs:
inputName: value
anotherInput: value
condition: succeeded()
continueOnError: false
enabled: true
env:
ENV_VAR: value
timeoutInMinutes: 0
.NET/C# Tasks
DotNetCoreCLI@2
Build, test, package, or publish a .NET Core project.
- task: DotNetCoreCLI@2
displayName: 'Restore NuGet packages'
inputs:
command: 'restore'
projects: '**/*.csproj'
- task: DotNetCoreCLI@2
displayName: 'Build'
inputs:
command: 'build'
projects: '**/*.csproj'
arguments: '--configuration Release'
- task: DotNetCoreCLI@2
displayName: 'Run Tests'
inputs:
command: 'test'
projects: '**/*Tests/*.csproj'
arguments: '--configuration Release --collect:"XPlat Code Coverage"'
- task: DotNetCoreCLI@2
displayName: 'Publish'
inputs:
command: 'publish'
projects: '**/*.csproj'
arguments: '--configuration Release --output $(Build.ArtifactStagingDirectory)'
zipAfterPublish: true
NuGetCommand@2
Restore, pack, or push NuGet packages.
- task: NuGetCommand@2
displayName: 'NuGet restore'
inputs:
command: 'restore'
restoreSolution: '**/*.sln'
- task: NuGetCommand@2
displayName: 'NuGet pack'
inputs:
command: 'pack'
packagesToPack: '**/*.nuspec'
versioningScheme: 'byBuildNumber'
Node.js/JavaScript Tasks
NodeTool@0
Install a specific Node.js version.
- task: NodeTool@0
displayName: 'Use Node.js 20.x'
inputs:
versionSpec: '20.x'
Npm@1
Run npm commands.
- task: Npm@1
displayName: 'npm install'
inputs:
command: 'install'
- task: Npm@1
displayName: 'npm run build'
inputs:
command: 'custom'
customCommand: 'run build'
- task: Npm@1
displayName: 'npm test'
inputs:
command: 'custom'
customCommand: 'run test'
Python Tasks
UsePythonVersion@0
Select a Python version to run on an agent.
- task: UsePythonVersion@0
displayName: 'Use Python 3.11'
inputs:
versionSpec: '3.11'
addToPath: true
architecture: 'x64'
Pip@1 / Script
Install Python packages.
- script: |
python -m pip install --upgrade pip
pip install -r requirements.txt
displayName: 'Install dependencies'
- script: pytest tests/ --junitxml=junit/test-results.xml
displayName: 'Run tests'
Docker Tasks
Docker@2
Build, push, or run Docker images.
# Login to registry
- task: Docker@2
displayName: 'Docker Login'
inputs:
command: 'login'
containerRegistry: 'myDockerRegistryServiceConnection'
# Build image
- task: Docker@2
displayName: 'Build Docker image'
inputs:
command: 'build'
repository: 'myrepo/myimage'
dockerfile: '$(Build.SourcesDirectory)/Dockerfile'
tags: |
$(Build.BuildId)
latest
# Push image
- task: Docker@2
displayName: 'Push Docker image'
inputs:
command: 'push'
repository: 'myrepo/myimage'
tags: |
$(Build.BuildId)
latest
# Build and push (combined)
- task: Docker@2
displayName: 'Build and Push'
inputs:
command: 'buildAndPush'
repository: 'myrepo/myimage'
dockerfile: '$(Build.SourcesDirectory)/Dockerfile'
containerRegistry: 'myDockerRegistryServiceConnection'
tags: |
$(Build.BuildId)
latest
DockerCompose@0
Build, push, or run multi-container Docker applications.
- task: DockerCompose@0
displayName: 'Run Docker Compose'
inputs:
action: 'Run services'
dockerComposeFile: 'docker-compose.yml'
projectName: '$(Build.Repository.Name)'
qualifyImageNames: true
buildImages: true
Kubernetes Tasks
Kubernetes@1
Deploy, configure, or update a Kubernetes cluster.
- task: Kubernetes@1
displayName: 'kubectl apply'
inputs:
connectionType: 'Kubernetes Service Connection'
kubernetesServiceEndpoint: 'myK8sConnection'
command: 'apply'
arguments: '-f manifests/'
- task: Kubernetes@1
displayName: 'kubectl set image'
inputs:
connectionType: 'Kubernetes Service Connection'
kubernetesServiceEndpoint: 'myK8sConnection'
command: 'set'
arguments: 'image deployment/myapp myapp=$(containerRegistry)/myimage:$(Build.BuildId)'
KubernetesManifest@0
Bake and deploy Kubernetes manifests.
- task: KubernetesManifest@0
displayName: 'Deploy to Kubernetes'
inputs:
action: 'deploy'
kubernetesServiceConnection: 'myK8sConnection'
namespace: 'production'
manifests: |
manifests/deployment.yml
manifests/service.yml
containers: '$(containerRegistry)/myimage:$(Build.BuildId)'
HelmDeploy@0
Deploy using Helm charts.
- task: HelmDeploy@0
displayName: 'Helm deploy'
inputs:
connectionType: 'Kubernetes Service Connection'
kubernetesServiceConnection: 'myK8sConnection'
namespace: 'production'
command: 'upgrade'
chartType: 'FilePath'
chartPath: '$(Build.SourcesDirectory)/charts/myapp'
releaseName: 'myapp'
arguments: '--set image.tag=$(Build.BuildId)'
Azure Tasks
AzureCLI@2
Run Azure CLI commands.
- task: AzureCLI@2
displayName: 'Azure CLI'
inputs:
azureSubscription: 'myAzureServiceConnection'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az --version
az account show
AzurePowerShell@5
Run Azure PowerShell commands.
- task: AzurePowerShell@5
displayName: 'Azure PowerShell'
inputs:
azureSubscription: 'myAzureServiceConnection'
scriptType: 'inlineScript'
inline: |
Get-AzResourceGroup
azurePowerShellVersion: 'LatestVersion'
AzureWebApp@1
Deploy to Azure App Service.
- task: AzureWebApp@1
displayName: 'Deploy to Azure Web App'
inputs:
azureSubscription: 'myAzureServiceConnection'
appType: 'webAppLinux'
appName: 'mywebapp'
package: '$(Build.ArtifactStagingDirectory)/**/*.zip'
AzureFunctionApp@1
Deploy to Azure Functions.
- task: AzureFunctionApp@1
displayName: 'Deploy Azure Function'
inputs:
azureSubscription: 'myAzureServiceConnection'
appType: 'functionAppLinux'
appName: 'myfunctionapp'
package: '$(Build.ArtifactStagingDirectory)/**/*.zip'
AzureRmWebAppDeployment@4
Advanced Azure App Service deployment.
- task: AzureRmWebAppDeployment@4
displayName: 'Azure App Service Deploy'
inputs:
azureSubscription: 'myAzureServiceConnection'
appType: 'webAppLinux'
WebAppName: 'mywebapp'
packageForLinux: '$(Build.ArtifactStagingDirectory)/**/*.zip'
RuntimeStack: 'NODE|20-lts'
Build and Artifact Tasks
PublishBuildArtifacts@1
Publish build artifacts to Azure Pipelines.
- task: PublishBuildArtifacts@1
displayName: 'Publish Artifact: drop'
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'drop'
publishLocation: 'Container'
DownloadBuildArtifacts@1
Download build artifacts.
- task: DownloadBuildArtifacts@1
displayName: 'Download Build Artifacts'
inputs:
buildType: 'current'
downloadType: 'single'
artifactName: 'drop'
downloadPath: '$(System.ArtifactsDirectory)'
PublishPipelineArtifact@1
Publish artifacts (preferred over PublishBuildArtifacts).
- task: PublishPipelineArtifact@1
displayName: 'Publish Pipeline Artifact'
inputs:
targetPath: '$(Build.ArtifactStagingDirectory)'
artifact: 'drop'
publishLocation: 'pipeline'
DownloadPipelineArtifact@2
Download pipeline artifacts (preferred over DownloadBuildArtifacts).
- task: DownloadPipelineArtifact@2
displayName: 'Download Pipeline Artifact'
inputs:
buildType: 'current'
artifactName: 'drop'
targetPath: '$(Pipeline.Workspace)'
Test and Code Coverage Tasks
PublishTestResults@2
Publish test results.
- task: PublishTestResults@2
displayName: 'Publish Test Results'
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '**/test-results.xml'
searchFolder: '$(System.DefaultWorkingDirectory)'
mergeTestResults: true
failTaskOnFailedTests: true
PublishCodeCoverageResults@1
Publish code coverage results.
- task: PublishCodeCoverageResults@1
displayName: 'Publish Code Coverage'
inputs:
codeCoverageTool: 'Cobertura'
summaryFileLocation: '$(System.DefaultWorkingDirectory)/**/coverage.xml'
reportDirectory: '$(System.DefaultWorkingDirectory)/**/htmlcov'
Utility Tasks
CopyFiles@2
Copy files to a target folder.
- task: CopyFiles@2
displayName: 'Copy Files'
inputs:
SourceFolder: '$(Build.SourcesDirectory)'
Contents: |
**/*.js
**/*.json
!node_modules/**
TargetFolder: '$(Build.ArtifactStagingDirectory)'
CleanTargetFolder: true
DeleteFiles@1
Delete files from the agent.
- task: DeleteFiles@1
displayName: 'Delete Files'
inputs:
SourceFolder: '$(Build.SourcesDirectory)'
Contents: |
**/node_modules
**/temp
Cache@2
Cache files and restore them in future runs.
- task: Cache@2
displayName: 'Cache npm'
inputs:
key: 'npm | "$(Agent.OS)" | package-lock.json'
restoreKeys: |
npm | "$(Agent.OS)"
path: $(Pipeline.Workspace)/.npm
- task: Cache@2
displayName: 'Cache Maven'
inputs:
key: 'maven | "$(Agent.OS)" | **/pom.xml'
restoreKeys: |
maven | "$(Agent.OS)"
path: $(Pipeline.Workspace)/.m2/repository
PowerShell@2 / Bash@3
Run PowerShell or Bash scripts.
- task: PowerShell@2
displayName: 'Run PowerShell Script'
inputs:
targetType: 'inline'
script: |
Write-Host "Running PowerShell"
Get-ChildItem -Path $(Build.SourcesDirectory)
- task: Bash@3
displayName: 'Run Bash Script'
inputs:
targetType: 'inline'
script: |
echo "Running Bash"
ls -la $(Build.SourcesDirectory)
Security Tasks
SonarCloudPrepare@1 / SonarCloudAnalyze@1
SonarCloud code analysis.
- task: SonarCloudPrepare@1
inputs:
SonarCloud: 'SonarCloud'
organization: 'myorg'
scannerMode: 'CLI'
configMode: 'manual'
cliProjectKey: 'myproject'
cliProjectName: 'My Project'
- task: SonarCloudAnalyze@1
displayName: 'Run SonarCloud Analysis'
- task: SonarCloudPublish@1
displayName: 'Publish SonarCloud Results'
inputs:
pollingTimeoutSec: '300'
WhiteSource@21
WhiteSource security and license scanning.
- task: WhiteSource@21
inputs:
cwd: '$(Build.SourcesDirectory)'
projectName: 'MyProject'
Version Control Tasks
GitHubRelease@1
Create a GitHub release.
- task: GitHubRelease@1
displayName: 'Create GitHub Release'
inputs:
gitHubConnection: 'GitHubServiceConnection'
repositoryName: '$(Build.Repository.Name)'
action: 'create'
target: '$(Build.SourceVersion)'
tagSource: 'gitTag'
tag: '$(Build.BuildNumber)'
title: 'Release $(Build.BuildNumber)'
releaseNotesSource: 'inline'
releaseNotesInline: 'Release notes here'
assets: '$(Build.ArtifactStagingDirectory)/**'
Best Practices
- Pin task versions: Always specify task version (e.g.,
@2, not@0) - Use displayName: Add clear display names for all tasks
- Cache dependencies: Use Cache@2 for package managers
- Use conditions: Control task execution with conditions
- Set timeouts: Prevent hung tasks with timeoutInMinutes
- Use service connections: Store credentials in service connections, not in pipeline
- Publish test results: Always publish test results for visibility
- Use artifact tasks: Prefer PublishPipelineArtifact over PublishBuildArtifacts
- Error handling: Use continueOnError for non-critical tasks
- Environment variables: Pass sensitive data via env, not as task inputs
Finding Task Documentation
For detailed task documentation:
- Official Task Reference: https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/
- Task Source Code: https://github.com/microsoft/azure-pipelines-tasks
- Use WebSearch: Search for “[TaskName] Azure Pipelines task documentation”
- Use Context7: Query for specific task documentation via MCP
Task Versioning
Tasks follow semantic versioning:
# Major version (recommended)
- task: TaskName@2
# Full version (for specific fixes)
- task: TaskName@2.3.1
Always use the latest major version unless you have specific compatibility requirements.
Custom Tasks
You can also create and use custom tasks from:
- Azure DevOps Marketplace
- Private extensions
- YAML templates
# Marketplace task
- task: PublisherName.ExtensionName.TaskName@version
# Template as reusable task
- template: templates/my-custom-task.yml
parameters:
parameter1: value
Azure Pipelines Templates Guide
This guide covers how to create and use templates in Azure Pipelines for reusable and maintainable pipeline configurations.
What Are Templates?
Templates allow you to define reusable content, logic, and parameters in YAML pipelines. They promote DRY (Don’t Repeat Yourself) principles and make pipelines more maintainable.
Template Types
1. Step Templates
Reusable sets of steps.
template: templates/build-steps.yml
steps:
- task: NodeTool@0
displayName: 'Install Node.js'
inputs:
versionSpec: '20.x'
- script: npm ci
displayName: 'Install dependencies'
- script: npm run build
displayName: 'Build application'
- script: npm test
displayName: 'Run tests'
Usage
jobs:
- job: Build
steps:
- template: templates/build-steps.yml
2. Job Templates
Reusable job definitions.
templates/test-job.yml
parameters:
- name: nodeVersion
type: string
default: '20'
- name: osImage
type: string
default: 'ubuntu-22.04'
jobs:
- job: Test_Node_${{ parameters.nodeVersion }}
displayName: 'Test on Node ${{ parameters.nodeVersion }}'
pool:
vmImage: ${{ parameters.osImage }}
steps:
- task: NodeTool@0
inputs:
versionSpec: ${{ parameters.nodeVersion }}
- script: npm ci
displayName: 'Install dependencies'
- script: npm test
displayName: 'Run tests'
Usage
stages:
- stage: Test
jobs:
- template: templates/test-job.yml
parameters:
nodeVersion: '18'
osImage: 'ubuntu-22.04'
- template: templates/test-job.yml
parameters:
nodeVersion: '20'
osImage: 'ubuntu-22.04'
3. Stage Templates
Reusable stage definitions.
templates/deploy-stage.yml
parameters:
- name: environment
type: string
- name: dependsOn
type: object
default: []
stages:
- stage: Deploy_${{ parameters.environment }}
displayName: 'Deploy to ${{ parameters.environment }}'
dependsOn: ${{ parameters.dependsOn }}
jobs:
- deployment: Deploy
displayName: 'Deploy Application'
environment: ${{ parameters.environment }}
strategy:
runOnce:
deploy:
steps:
- script: echo "Deploying to ${{ parameters.environment }}"
displayName: 'Deploy'
Usage
stages:
- stage: Build
jobs: [...]
- template: templates/deploy-stage.yml
parameters:
environment: 'staging'
dependsOn: Build
- template: templates/deploy-stage.yml
parameters:
environment: 'production'
dependsOn: Build
4. Variable Templates
Reusable variable definitions.
templates/variables-common.yml
variables:
nodeVersion: '20'
buildConfiguration: 'Release'
artifactName: 'drop'
Usage
variables:
- template: templates/variables-common.yml
- name: customVariable
value: 'customValue'
Template Parameters
Parameter Types
parameters:
# String
- name: environmentName
type: string
default: 'dev'
# Number
- name: timeout
type: number
default: 30
# Boolean
- name: runTests
type: boolean
default: true
# Object (list)
- name: environments
type: object
default:
- dev
- staging
- prod
# Object (dictionary)
- name: settings
type: object
default:
debug: true
verbose: false
# Step list
- name: buildSteps
type: stepList
default: []
# Job list
- name: testJobs
type: jobList
default: []
# Stage list
- name: deployStages
type: stageList
default: []
Parameter Validation
parameters:
- name: environment
type: string
values: # Restrict to specific values
- dev
- staging
- production
- name: version
type: string
default: '1.0.0'
Using Parameters
# String interpolation
steps:
- script: echo "Deploying to ${{ parameters.environment }}"
displayName: 'Deploy to ${{ parameters.environment }}'
# Conditional logic
- ${{ if eq(parameters.runTests, true) }}:
- script: npm test
displayName: 'Run tests'
# Object iteration
- ${{ each env in parameters.environments }}:
- script: echo "Deploying to ${{ env }}"
displayName: 'Deploy to ${{ env }}'
Template Expressions
Conditional Insertion
parameters:
- name: runTests
type: boolean
default: true
steps:
- script: npm run build
displayName: 'Build'
- ${{ if eq(parameters.runTests, true) }}:
- script: npm test
displayName: 'Run tests'
- ${{ if ne(parameters.runTests, true) }}:
- script: echo "Skipping tests"
displayName: 'Skip tests'
Iteration
parameters:
- name: nodeVersions
type: object
default:
- '18'
- '20'
- '22'
strategy:
matrix:
${{ each version in parameters.nodeVersions }}:
Node_${{ version }}:
nodeVersion: ${{ version }}
Each with Key-Value Pairs
parameters:
- name: environments
type: object
default:
dev:
url: https://dev.example.com
staging:
url: https://staging.example.com
prod:
url: https://prod.example.com
stages:
- ${{ each env in parameters.environments }}:
- stage: Deploy_${{ env.key }}
jobs:
- job: Deploy
variables:
targetUrl: ${{ env.value.url }}
steps:
- script: echo "Deploying to ${{ env.value.url }}"
Extends Template
The extends keyword allows you to extend an entire pipeline template.
templates/secure-pipeline.yml
parameters:
- name: buildSteps
type: stepList
default: []
stages:
- stage: Build
jobs:
- job: SecurityScan
steps:
- script: echo "Running security scan"
- job: Build
steps:
- script: echo "Pre-build checks"
- ${{ each step in parameters.buildSteps }}:
- ${{ step }}
- script: echo "Post-build checks"
azure-pipelines.yml
extends:
template: templates/secure-pipeline.yml
parameters:
buildSteps:
- script: npm ci
displayName: 'Install dependencies'
- script: npm run build
displayName: 'Build application'
Template Inclusion
Local Templates
# Relative path from current file
- template: templates/build-steps.yml
# Relative path with parameters
- template: ../shared/deploy.yml
parameters:
environment: production
Templates from Other Repositories
resources:
repositories:
- repository: templates
type: github
name: myorg/pipeline-templates
ref: refs/heads/main
stages:
- template: build-stage.yml@templates
parameters:
projectName: myapp
Templates from Different Branches
resources:
repositories:
- repository: templates
type: git
name: MyProject/Templates
ref: refs/heads/v2
jobs:
- template: ci-job.yml@templates
Advanced Template Patterns
Matrix Build Template
templates/matrix-test.yml
parameters:
- name: operatingSystems
type: object
default:
- ubuntu-22.04
- windows-2022
- macOS-12
- name: nodeVersions
type: object
default:
- '18'
- '20'
jobs:
- job: Test
strategy:
matrix:
${{ each os in parameters.operatingSystems }}:
${{ each version in parameters.nodeVersions }}:
${{ os }}_Node_${{ version }}:
imageName: ${{ os }}
nodeVersion: ${{ version }}
pool:
vmImage: $(imageName)
steps:
- task: NodeTool@0
inputs:
versionSpec: $(nodeVersion)
- script: npm test
Conditional Stage Template
templates/optional-deploy.yml
parameters:
- name: shouldDeploy
type: boolean
default: false
- name: environment
type: string
stages:
- ${{ if eq(parameters.shouldDeploy, true) }}:
- stage: Deploy_${{ parameters.environment }}
jobs:
- deployment: Deploy
environment: ${{ parameters.environment }}
strategy:
runOnce:
deploy:
steps:
- script: echo "Deploying"
Nested Templates
templates/full-pipeline.yml
parameters:
- name: runTests
type: boolean
default: true
stages:
- stage: Build
jobs:
- template: build-job.yml # Nested template
- ${{ if eq(parameters.runTests, true) }}:
- template: test-stage.yml # Nested template
- template: deploy-stage.yml # Nested template
parameters:
environment: production
Template Best Practices
1. Parameter Documentation
# templates/deploy.yml
# Deploys application to specified environment
# Parameters:
# environment: Target environment (dev, staging, prod)
# version: Application version to deploy
# approvalRequired: Whether manual approval is needed
parameters:
- name: environment
type: string
displayName: 'Target Environment'
- name: version
type: string
default: 'latest'
displayName: 'Application Version'
- name: approvalRequired
type: boolean
default: true
displayName: 'Require Manual Approval'
2. Default Values
Always provide sensible defaults for parameters.
parameters:
- name: buildConfiguration
type: string
default: 'Release'
- name: runTests
type: boolean
default: true
3. Template Validation
Use parameter restrictions to validate inputs.
parameters:
- name: environment
type: string
values:
- dev
- staging
- production
4. Template Organization
templates/
├── stages/
│ ├── build-stage.yml
│ ├── test-stage.yml
│ └── deploy-stage.yml
├── jobs/
│ ├── build-job.yml
│ └── test-job.yml
├── steps/
│ ├── npm-build.yml
│ └── docker-build.yml
└── variables/
├── common.yml
└── production.yml
5. Versioning Templates
Use tags or branches to version your template repository.
resources:
repositories:
- repository: templates
type: github
name: myorg/pipeline-templates
ref: refs/tags/v2.1.0 # Specific version tag
Common Template Patterns
Build and Test Template
templates/build-and-test.yml
parameters:
- name: projectPath
type: string
default: '.'
- name: nodeVersion
type: string
default: '20'
steps:
- task: NodeTool@0
inputs:
versionSpec: ${{ parameters.nodeVersion }}
- task: Cache@2
inputs:
key: 'npm | "$(Agent.OS)" | ${{ parameters.projectPath }}/package-lock.json'
path: $(Pipeline.Workspace)/.npm
- script: npm ci --cache $(Pipeline.Workspace)/.npm
workingDirectory: ${{ parameters.projectPath }}
displayName: 'Install dependencies'
- script: npm run build
workingDirectory: ${{ parameters.projectPath }}
displayName: 'Build'
- script: npm test
workingDirectory: ${{ parameters.projectPath }}
displayName: 'Test'
Docker Build Template
templates/docker-build.yml
parameters:
- name: dockerfilePath
type: string
default: 'Dockerfile'
- name: imageName
type: string
- name: imageTag
type: string
default: '$(Build.BuildId)'
steps:
- task: Docker@2
displayName: 'Build Docker image'
inputs:
command: 'build'
repository: ${{ parameters.imageName }}
dockerfile: ${{ parameters.dockerfilePath }}
tags: |
${{ parameters.imageTag }}
latest
- task: Docker@2
displayName: 'Push Docker image'
inputs:
command: 'push'
repository: ${{ parameters.imageName }}
tags: |
${{ parameters.imageTag }}
latest
Deployment Approval Template
templates/deploy-with-approval.yml
parameters:
- name: environment
type: string
- name: serviceConnection
type: string
stages:
- stage: Deploy_${{ parameters.environment }}
jobs:
- deployment: Deploy
environment: ${{ parameters.environment }} # Approval configured in environment
strategy:
runOnce:
deploy:
steps:
- task: AzureWebApp@1
inputs:
azureSubscription: ${{ parameters.serviceConnection }}
appName: 'myapp-${{ parameters.environment }}'
Debugging Templates
View Expanded Template
Use the Azure DevOps UI to view the fully expanded YAML after template processing.
Template Syntax Validation
# Use template expressions for debugging
- script: echo "Environment: ${{ parameters.environment }}"
displayName: 'Debug: Show parameters'
Summary
Templates are powerful for:
- Reusability: Write once, use many times
- Maintainability: Update in one place
- Consistency: Enforce standards across pipelines
- Modularity: Break complex pipelines into manageable pieces
Key takeaways:
- Use parameters for flexibility
- Provide sensible defaults
- Document your templates
- Organize templates logically
- Version your template repositories
- Test templates thoroughly
Further Reading
Typical Workflow Example
This document provides a concrete example of the end-to-end process for generating a complex Azure Pipeline.
Example Request
User request: “Create a CI/CD pipeline for a Node.js app with Docker deployment to AKS”
Step-by-Step Process
1. UNDERSTAND — Node.js build/test → Docker image → push to ACR → deploy to AKS (staging + prod)
2. READ — references/yaml-schema.md, tasks-reference.md, best-practices.md
assets/examples/multi-stage-cicd.yml, kubernetes-deploy.yml
3. LOOKUP — Context7 or WebSearch for any tasks not covered locally
4. GENERATE — Stage 1: NodeTool@0 + npm ci (cached) + build + test + PublishTestResults
Stage 2: Docker@2 buildAndPush (tag with BuildId + latest)
Stage 3: Deployment job → KubernetesManifest@0 → health check
5. VALIDATE — Invoke devops-skills:azure-pipelines-validator; fix errors; re-validate
6. PRESENT — Validated pipeline + stage explanation + setup notes (service connections, environments)
Workflow Components Explained
1. UNDERSTAND
Break down the user’s request into technical requirements:
- Build: Node.js application needs runtime setup, dependency installation, compilation
- Test: Run automated tests and publish results
- Package: Create Docker container image
- Publish: Push image to Azure Container Registry (ACR)
- Deploy: Deploy to Azure Kubernetes Service across multiple environments
2. READ
Load relevant documentation and examples:
- Core schema and task references for syntax accuracy
- Best practices for security, performance, naming conventions
- Example files matching the pipeline type (multi-stage, Kubernetes)
3. LOOKUP
Use external documentation sources when needed:
- Tasks not covered in local references
- Version-specific behavior questions
- Troubleshooting specific errors
4. GENERATE
Create the pipeline following:
- Multi-stage structure with proper dependencies
- Security best practices (service connections, no hardcoded secrets)
- Performance optimizations (caching, parallelism)
- Proper error handling and test result publishing
5. VALIDATE
Critical step: Run validation before delivery:
# Invoke validator skill
devops-skills:azure-pipelines-validator
Fix any reported issues and re-validate until clean.
6. PRESENT
Deliver complete solution with:
- Validated pipeline YAML
- Explanation of each stage’s purpose
- Setup instructions (service connections, environment configuration)
- Any additional configuration needed in Azure DevOps
Azure Pipelines YAML Schema Reference
This document provides a comprehensive reference for Azure Pipelines YAML syntax and structure.
Pipeline Structure
Azure Pipelines follow a hierarchical structure:
Pipeline
└── Stages
└── Jobs
└── Steps
Basic Pipeline
# Minimal pipeline with implicit stage
trigger:
- main
pool:
vmImage: 'ubuntu-22.04'
steps:
- script: echo "Hello World"
displayName: 'Run a one-line script'
Multi-Stage Pipeline
stages:
- stage: Build
displayName: 'Build Stage'
jobs:
- job: BuildJob
displayName: 'Build Job'
pool:
vmImage: 'ubuntu-22.04'
steps:
- script: npm run build
displayName: 'Build Application'
- stage: Deploy
displayName: 'Deploy Stage'
dependsOn: Build
jobs:
- deployment: DeployJob
displayName: 'Deploy to Production'
environment: production
strategy:
runOnce:
deploy:
steps:
- script: echo "Deploying..."
displayName: 'Deploy'
Root-Level Keywords
trigger
Defines CI triggers (when the pipeline should run automatically).
# Simple trigger
trigger:
- main
- develop
# Advanced trigger with path filters
trigger:
branches:
include:
- main
- release/*
exclude:
- feature/*
paths:
include:
- src/**
exclude:
- docs/**
tags:
include:
- v*
pr
Defines pull request triggers.
pr:
branches:
include:
- main
paths:
exclude:
- docs/**
schedules
Defines scheduled triggers (cron syntax).
schedules:
- cron: "0 0 * * *"
displayName: Daily midnight build
branches:
include:
- main
always: true
resources
Defines external resources used by the pipeline.
resources:
repositories:
- repository: templates
type: github
name: org/repo
ref: refs/heads/main
pipelines:
- pipeline: upstream
source: 'Upstream Pipeline'
trigger:
branches:
- main
containers:
- container: linux
image: ubuntu:22.04
packages:
- package: mypackage
type: npm
connection: npmConnection
name: '@scope/package'
version: '1.0.0'
pool
Defines the default agent pool for all jobs.
# Microsoft-hosted agent
pool:
vmImage: 'ubuntu-22.04'
# Self-hosted agent pool
pool:
name: 'MyAgentPool'
demands:
- agent.os -equals Linux
variables
Defines pipeline-level variables.
variables:
buildConfiguration: 'Release'
vmImage: 'ubuntu-22.04'
# Variable groups
variables:
- group: 'my-variable-group'
- name: customVar
value: 'customValue'
# Template variables
variables:
- template: variables-template.yml
parameters
Defines runtime parameters (user input when pipeline runs).
parameters:
- name: environment
displayName: 'Target Environment'
type: string
default: 'staging'
values:
- dev
- staging
- production
- name: runTests
displayName: 'Run Tests'
type: boolean
default: true
- name: regions
displayName: 'Deployment Regions'
type: object
default:
- westus
- eastus
Stages
Stages represent major divisions in your pipeline (e.g., Build, Test, Deploy).
stages:
- stage: Build
displayName: 'Build Stage'
# Stage condition
condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')
# Stage dependencies
dependsOn: [] # No dependencies, can run immediately
# Stage variables
variables:
stageVar: 'value'
jobs:
- job: BuildJob
steps:
- script: npm run build
Stage Properties
stage: Unique identifierdisplayName: Human-readable namedependsOn: List of stages to wait forcondition: Condition to run the stagevariables: Stage-specific variablesjobs: Jobs to run in this stage
Jobs
Jobs represent a series of steps that run sequentially on the same agent.
Regular Job
jobs:
- job: BuildJob
displayName: 'Build Application'
# Job timeout (default: 60 minutes)
timeoutInMinutes: 30
# Job cancellation timeout
cancelTimeoutInMinutes: 5
# Pool for this job
pool:
vmImage: 'ubuntu-22.04'
# Job dependencies
dependsOn: []
# Job condition
condition: succeeded()
# Continue on error
continueOnError: false
# Job variables
variables:
jobVar: 'value'
# Job strategy (matrix, parallel)
strategy:
matrix:
linux:
imageName: 'ubuntu-22.04'
mac:
imageName: 'macOS-12'
windows:
imageName: 'windows-2022'
steps:
- script: npm run build
Deployment Job
Deployment jobs are special jobs for deploying to environments with deployment history and approvals.
jobs:
- deployment: DeployWeb
displayName: 'Deploy Web App'
# Target environment
environment:
name: production
resourceName: web-app
resourceType: Kubernetes
# Deployment strategy
strategy:
runOnce:
preDeploy:
steps:
- script: echo "Pre-deploy"
deploy:
steps:
- script: echo "Deploying"
routeTraffic:
steps:
- script: echo "Routing traffic"
postRouteTraffic:
steps:
- script: echo "Post routing"
on:
failure:
steps:
- script: echo "Deployment failed"
success:
steps:
- script: echo "Deployment succeeded"
Deployment Strategies
runOnce
strategy:
runOnce:
deploy:
steps:
- script: echo "Deploying"
rolling
strategy:
rolling:
maxParallel: 2
preDeploy:
steps:
- script: echo "Pre-deploy"
deploy:
steps:
- script: echo "Deploy"
postDeploy:
steps:
- script: echo "Post-deploy"
canary
strategy:
canary:
increments: [10, 20, 50]
preDeploy:
steps:
- script: echo "Pre-deploy"
deploy:
steps:
- script: echo "Deploy $(strategy.canary.increment)%"
postDeploy:
steps:
- script: echo "Post-deploy"
Steps
Steps are the individual tasks that run in a job.
Script Step
steps:
- script: echo "Hello World"
displayName: 'Run Script'
workingDirectory: $(Build.SourcesDirectory)
failOnStderr: false
condition: succeeded()
env:
MY_VAR: value
Bash Step
steps:
- bash: |
echo "Multi-line bash script"
npm install
npm test
displayName: 'Run Bash Script'
PowerShell Step
steps:
- powershell: |
Write-Host "PowerShell script"
Get-ChildItem
displayName: 'Run PowerShell'
Task Step
steps:
- task: TaskName@version
displayName: 'Task Display Name'
inputs:
inputName: value
condition: succeeded()
continueOnError: false
enabled: true
env:
VARIABLE: value
timeoutInMinutes: 0
Checkout Step
steps:
- checkout: self
clean: true
fetchDepth: 1
lfs: false
submodules: false
persistCredentials: false
Download Step
steps:
- download: current
artifact: artifactName
patterns: '**/*.zip'
- download: upstream
artifact: artifactName
Publish Step
steps:
- publish: $(Build.ArtifactStagingDirectory)
artifact: drop
displayName: 'Publish Artifact'
Conditions
Conditions control when stages, jobs, or steps run.
Built-in Conditions
condition: succeeded() # Previous succeeded (default)
condition: failed() # Previous failed
condition: succeededOrFailed() # Previous completed
condition: always() # Always run
condition: canceled() # Pipeline was canceled
Custom Conditions
# Variable equality
condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')
# Contains check
condition: contains(variables['Build.SourceBranch'], 'release')
# And/Or/Not
condition: and(succeeded(), eq(variables['environment'], 'prod'))
condition: or(eq(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.Reason'], 'Manual'))
condition: not(eq(variables['Skip'], 'true'))
# StartsWith/EndsWith
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/feature/')
condition: endsWith(variables['artifactName'], '.zip')
Expressions and Variables
Predefined Variables
# Build variables
$(Build.BuildId)
$(Build.BuildNumber)
$(Build.SourceBranch)
$(Build.SourceVersion)
$(Build.Reason)
$(Build.ArtifactStagingDirectory)
$(Build.SourcesDirectory)
# Agent variables
$(Agent.OS)
$(Agent.MachineName)
$(Agent.WorkFolder)
# System variables
$(System.TeamProject)
$(System.StageName)
$(System.JobName)
$(System.HostType)
# Pipeline variables
$(Pipeline.Workspace)
Variable Syntax
# Macro syntax (processed at queue time)
$(variableName)
# Template expression syntax (processed at compile time)
${{ variables.variableName }}
# Runtime expression syntax (processed at runtime)
$[variables.variableName]
Accessing Job Outputs
# In the same stage
dependencies.jobName.outputs['stepName.variableName']
# Across stages
stageDependencies.stageName.jobName.outputs['stepName.variableName']
Template Syntax
Template Reference
# Include template
- template: path/to/template.yml
parameters:
paramName: value
# Extends template
extends:
template: path/to/template.yml
parameters:
paramName: value
Template Parameters
# In template file
parameters:
- name: paramName
type: string
default: defaultValue
- name: paramList
type: object
default: []
# Use parameters
steps:
- script: echo ${{ parameters.paramName }}
Template Iteration
# Iterate over parameters
parameters:
- name: environments
type: object
default:
- dev
- staging
- prod
stages:
- ${{ each env in parameters.environments }}:
- stage: Deploy_${{ env }}
jobs:
- job: DeployTo${{ env }}
steps:
- script: echo "Deploying to ${{ env }}"
Container Jobs
Run jobs in Docker containers.
resources:
containers:
- container: node
image: node:20-alpine
jobs:
- job: BuildInContainer
container: node
steps:
- script: npm install
- script: npm test
Service Containers
Run sidecar containers alongside your job.
resources:
containers:
- container: postgres
image: postgres:15
env:
POSTGRES_PASSWORD: password
ports:
- 5432:5432
jobs:
- job: Test
services:
postgres: postgres
steps:
- script: npm test
env:
DATABASE_URL: postgres://postgres:password@postgres:5432/test
Matrix Strategy
Run the same job with different variable combinations.
strategy:
matrix:
linux_node18:
imageName: 'ubuntu-22.04'
nodeVersion: '18'
linux_node20:
imageName: 'ubuntu-22.04'
nodeVersion: '20'
mac_node18:
imageName: 'macOS-12'
nodeVersion: '18'
maxParallel: 3
pool:
vmImage: $(imageName)
steps:
- task: NodeTool@0
inputs:
versionSpec: $(nodeVersion)
- script: npm test
Environment and Approvals
Environments provide deployment history, approvals, and checks.
jobs:
- deployment: DeployProd
environment:
name: production
resourceName: web-app
strategy:
runOnce:
deploy:
steps:
- script: echo "Deploying"
Best Practices Summary
- Use specific versions: Pin
vmImageand task versions - Use displayName: Add clear display names for readability
- Use stages: Organize complex pipelines with stages
- Use templates: Create reusable templates for common patterns
- Use conditions: Control execution flow with conditions
- Use dependsOn: Optimize with explicit dependencies
- Use environments: Track deployments with environments
- Use parameters: Make pipelines configurable with runtime parameters
- Cache dependencies: Use Cache task for package managers
- Set timeouts: Prevent hung jobs with timeout settings