
Azure Pipelines Generator
- 413 installs
- 286 repo stars
- Updated July 26, 2026
- akin-ozer/cc-devops-skills
azure-pipelines-generator is a Claude Code skill in akin-ozer/cc-devops-skills that generates Azure Pipelines YAML with build, test, and deploy stages for developers who need CI/CD configuration quickly without manual pi
About
azure-pipelines-generator is a DevOps skill from akin-ozer/cc-devops-skills that drafts Azure Pipelines YAML covering build, test, and deploy stages. It targets teams onboarding Azure DevOps CI/CD who want agent-generated pipeline files instead of copying snippets by hand. Developers reach for azure-pipelines-generator when standing up a new service pipeline, adding test gates, or wiring deployment stages into azure-pipelines.yml. The catalog description emphasizes speed for teams that know their stack but not every Azure Pipelines syntax detail.
- Scaffold Azure Pipelines YAML faster
- Encode build, test, and deploy stages consistently
- Reduce CI config syntax mistakes
- Align pipelines with repo structure
- Accelerate Azure DevOps onboarding
Azure Pipelines Generator by the numbers
- 413 all-time installs (skills.sh)
- Ranked #291 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/akin-ozer/cc-devops-skills --skill azure-pipelines-generatorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 413 |
|---|---|
| repo stars | ★ 286 |
| Last updated | July 26, 2026 |
| Repository | akin-ozer/cc-devops-skills ↗ |
How do you generate Azure Pipelines YAML for CI/CD?
Generate Azure Pipelines YAML for build, test, and deploy stages so teams can stand up CI/CD quickly without hand-writing boilerplate pipeline config.
Who is it for?
Development teams adopting Azure DevOps who need starter pipeline YAML with build, test, and deploy stages generated from project requirements.
Skip if: GitHub Actions, GitLab CI, or Jenkins pipelines when Azure Pipelines is not the target platform.
When should I use this skill?
The user asks to create or scaffold Azure Pipelines YAML with build, test, and deploy stages for a repository.
What you get
Azure Pipelines YAML defining build, test, and deploy stages for Azure DevOps pipelines.
- azure-pipelines.yml YAML
- Build stage definition
- Test and deploy stage definitions
By the numbers
- Generates YAML covering 3 pipeline stages: build, test, and deploy
Files
Azure Pipelines Generator
Generate production-ready Azure DevOps pipeline YAML with deterministic steps, explicit fallbacks, and clear completion criteria.
Trigger Guidance
Use this skill when the user asks to generate or redesign Azure Pipelines YAML, for example:
- "Create
azure-pipelines.ymlfor my Node service." - "Build a multi-stage Azure DevOps pipeline with staging and production."
- "Generate Azure pipeline templates for reuse across repos."
- "Convert this CI flow to Azure Pipelines."
Do not use this skill for validation-only requests. For validation-only work, use azure-pipelines-validator.
Execution Model
Normative keywords:
MUST: requiredSHOULD: default unless user asks otherwiseMAY: optional
Deterministic sequence:
1. Classify request mode. 2. Capture minimum required inputs. 3. Load minimum references (progressive disclosure). 4. Generate YAML using the quality checklist. 5. Validate (validator skill, script fallback, or manual fallback). 6. Return output in the required report format.
If a step cannot run due to environment limits, use the fallback in that step and continue.
1) Classify Request Mode
Choose exactly one primary mode:
- Basic CI: build/test/lint for one stack.
- Multi-stage CI/CD: build -> test -> deploy with environment tracking.
- Docker: image build/push and optional deploy.
- Kubernetes: image build/push plus Kubernetes deployment.
- Language-specific: .NET, Node.js, Python, Go, Java focused.
- Template-based: reusable templates plus thin root pipeline.
- Snippet-only: partial YAML requested, not full pipeline.
Mode-to-example mapping:
- Basic CI ->
examples/basic-ci.yml - Multi-stage CI/CD ->
examples/multi-stage-cicd.yml - .NET ->
examples/dotnet-cicd.yml - Python ->
examples/python-cicd.yml - Go ->
examples/go-cicd.yml - Kubernetes ->
examples/kubernetes-deploy.yml - Template-based ->
examples/template-usage.yml+examples/templates/*.yml
2) Capture Required Inputs
Collect these before generation:
- App stack and package manager
- Build/test commands and report expectations
- Deployment target (none, Azure service, Docker registry, Kubernetes)
- Environment flow (dev/staging/prod) and branch gates
- Service connections, variable groups, secret handling
- Template requirement (yes/no)
Safe defaults when missing:
- CI branches:
main,develop - Production deploy branch:
mainonly - Agent image: pinned image (for example
ubuntu-22.04) - Deploy image tag: immutable (
$(Build.BuildId)), never deploylatest
If key details are missing, state assumptions explicitly in final output.
3) Load References (Progressive Disclosure)
Read local references first.
Always read:
docs/yaml-schema.mddocs/best-practices.md
Read conditionally:
docs/tasks-reference.mdwhen selecting tasks/inputsdocs/templates-guide.mdonly for template-based mode
Then read only the closest example(s) from the mode mapping above.
Fallback behavior for missing references:
- Missing example: use nearest mode example and note substitution.
- Missing doc section: continue with known conventions and mark uncertainty.
- Snippet-only request: read only the minimum needed for safe output.
The final response MUST include:
References usedReferences skipped or missingImpact
4) External Docs Escalation (Only When Needed)
Escalate beyond local docs only when:
- required task info is not in local docs
- task version compatibility is unclear
- troubleshooting a task-specific failure
Use this order:
1. Context7 (mcp__context7__resolve-library-id -> mcp__context7__query-docs) 2. Official docs search (Microsoft Learn first)
If network/tools are unavailable, proceed with best-known local guidance and add a residual-risk note.
5) Pipeline Generation Checklist
Apply all items below unless user asks for a narrow snippet.
Security:
- Never hardcode secrets.
- Use service connections and variable groups/secrets.
- Use immutable deploy image tags.
Versioning:
- Pin
vmImageto explicit version, not*-latest. - Pin task major versions (
Task@N). @0is allowed only when that task uses major0.
Reliability:
- Use explicit
dependsOn. - Add
timeoutInMinutesfor long-running jobs. - Use branch-gated deployment
conditionrules. - Use deployment jobs with
environmentfor deploy stages.
Performance:
- Use
Cache@2where it improves dependency install time. - Use shallow checkout when full history is not required.
- Publish only required artifacts.
Testing/observability:
- Run lint/tests in CI.
- Publish test results with
condition: succeededOrFailed(). - Publish coverage when available.
Maintainability:
- Add
displayNamefor stages/jobs/key steps. - Use templates when logic repeats.
- Add short comments only for non-obvious logic.
6) Validation Workflow
Default path (MUST for full pipeline generation):
1. Generate or update YAML. 2. Validate with azure-pipelines-validator. 3. Fix findings. 4. Re-run validation until no blocking issues remain.
Script fallback if validator skill is unavailable but local validator scripts exist:
bash devops-skills-plugin/skills/azure-pipelines-validator/scripts/validate_azure_pipelines.sh <pipeline-file>Manual fallback when neither skill nor script can run:
1. YAML structure/indentation sanity 2. Hierarchy sanity (stages -> jobs -> steps) 3. Task format sanity (Task@Major) 4. Secret exposure scan (no plaintext credentials/tokens) 5. Deployment safety scan (environment usage, immutable deploy tags)
When fallback is used, final response MUST include:
Validation status: Manual fallbackChecks performedResidual risk
Validation MAY be skipped only for:
- snippet-only YAML
- documentation-only examples
- explicit user request to skip validation
7) Output Contract
Final response MUST include:
1. Pipeline YAML (or template set) 2. Required setup:
- service connections
- variable groups/secrets
- environments and approvals/checks
3. Validation result:
- validator status, script status, or manual fallback status
4. Assumptions 5. References used/skipped and impact 6. Optional next improvements
8) Canonical Example Flows
Example A: Full multi-stage generation
1. Select mode: Multi-stage CI/CD. 2. Capture stack/deploy/service-connection inputs. 3. Read docs/yaml-schema.md, docs/best-practices.md, and examples/multi-stage-cicd.yml. 4. Add requested customizations (stages, branch gates, environments, tasks). 5. Validate with azure-pipelines-validator; fix and re-run. 6. Return YAML + setup + validation + assumptions + references.
Example B: Quick snippet generation
1. Select mode: Snippet-only. 2. Read only the minimum required reference section. 3. Generate focused YAML snippet with safe defaults. 4. Skip full validation and state Validation status: Skipped (snippet-only). 5. Return snippet + assumptions + references.
9) Definition of Done
The execution is complete only when all applicable checks pass:
- Request mode is explicitly chosen.
- Assumptions are explicit for missing inputs.
- YAML follows checklist requirements (security, versioning, reliability, performance, maintainability).
- Validation path is documented (validator, script fallback, or manual fallback).
- Final response follows the output contract, including references and impact.
Azure Pipelines Best Practices
This document outlines best practices for creating maintainable, secure, and efficient Azure Pipelines.
How to Apply This Document
Use this deterministic sequence:
1. Apply Security and Version rules first. 2. Add reliability controls (dependsOn, condition, timeoutInMinutes). 3. Apply performance improvements (Cache@2, shallow checkout, scoped artifacts). 4. Add maintainability improvements (displayName, templates, concise comments). 5. Validate the resulting pipeline with azure-pipelines-validator.
Fallback behavior:
- If a recommended task or feature is unavailable in your environment, keep the closest safe alternative and note the tradeoff.
- If you cannot run automatic validation, use manual checks for secrets, task version pinning, hierarchy, and deployment safety.
- If constraints force intentional deviations, list each deviation and explain risk and mitigation.
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 secret2. 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 version3. 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 secret5. 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:backend3. Use Shallow Clone
Reduce clone time by limiting git history.
steps:
- checkout: self
clean: true
fetchDepth: 1 # Shallow clone4. 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 settings5. 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 timeMaintainability
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: true2. 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 issuesError Handling
1. Set Timeouts
jobs:
- job: Build
timeoutInMinutes: 30 # Prevent hung jobs
cancelTimeoutInMinutes: 52. 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 failsCI/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: production2. 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 lintCommon Anti-Patterns to Avoid
❌ Avoid
1. Using `latest` tags for images or tasks 2. Hardcoding secrets in pipeline files 3. Not using caching for dependencies 4. Not publishing test results 5. Long-running jobs without timeouts 6. Mixing stages/jobs/steps at root level 7. Using unpinned or outdated task major versions 8. Not using displayName 9. Creating monolithic single-stage pipelines for complex workflows 10. 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
Done Criteria
Best-practice application is complete when:
- All non-negotiable safeguards are present (no hardcoded secrets, pinned versions, immutable deploy tags).
- Reliability and observability controls are included for each build/deploy path.
- Any deviations from this guide are explicitly documented with rationale.
- Validation evidence is included (validator output or manual fallback checks).
Further Reading
Azure Pipelines Tasks Reference
This document provides a reference for commonly used built-in Azure Pipelines tasks.
How to Use This Reference
Use this deterministic flow:
1. Start from Task Syntax. 2. Jump to only the task family needed for the current pipeline. 3. Copy a minimal example and adjust inputs. 4. Keep task major versions pinned (Task@N). 5. Validate with azure-pipelines-validator after editing.
Fallback behavior:
- If a required task is not listed here, look up official docs (Microsoft Learn) and note the source.
- If external lookup is unavailable, keep the nearest known task pattern and mark uncertainty in assumptions.
- If input compatibility is unclear, prefer the latest documented stable major and avoid undocumented inputs.
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: trueNuGetCommand@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)
$(Build.SourceVersion)
# Push image
- task: Docker@2
displayName: 'Push Docker image'
inputs:
command: 'push'
repository: 'myrepo/myimage'
tags: |
$(Build.BuildId)
$(Build.SourceVersion)
# 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)
$(Build.SourceVersion)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: trueKubernetes 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 showAzurePowerShell@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: truePublishCodeCoverageResults@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: trueDeleteFiles@1
Delete files from the agent.
- task: DeleteFiles@1
displayName: 'Delete Files'
inputs:
SourceFolder: '$(Build.SourcesDirectory)'
Contents: |
**/node_modules
**/tempCache@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/repositoryPowerShell@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
1. Pin task versions: Always specify the task major version and use the latest supported major for that task (for example, Docker@2; @0 is valid when that task only ships major 0) 2. Use displayName: Add clear display names for all tasks 3. Cache dependencies: Use Cache@2 for package managers 4. Use conditions: Control task execution with conditions 5. Set timeouts: Prevent hung tasks with timeoutInMinutes 6. Use service connections: Store credentials in service connections, not in pipeline 7. Publish test results: Always publish test results for visibility 8. Use artifact tasks: Prefer PublishPipelineArtifact over PublishBuildArtifacts 9. Error handling: Use continueOnError for non-critical tasks 10. Environment variables: Pass sensitive data via env, not as task inputs
Finding Task Documentation
For detailed task documentation, use this order:
1. Context7: Resolve library ID, then query task-specific docs. 2. Official Task Reference: https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/ 3. Task Source Code: https://github.com/microsoft/azure-pipelines-tasks 4. Targeted web search: "[TaskName] Azure Pipelines task documentation" (prefer Microsoft Learn URLs)
Task Versioning
Tasks follow semantic versioning:
# Major version (recommended)
- task: TaskName@2
# Full version (for specific fixes)
- task: TaskName@2.3.1Pin a known-compatible major version for your environment. Upgrade deliberately after compatibility checks.
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: valueDone Criteria
Use of this reference is complete when:
- The selected task examples match the requested pipeline mode.
- Task majors are pinned and inputs are explicitly set where required.
- Any missing-task assumptions or external-source lookups are documented.
- The resulting pipeline is validated with
azure-pipelines-validator(or documented manual fallback).
Azure Pipelines Templates Guide
This guide covers how to create and use templates in Azure Pipelines for reusable and maintainable pipeline configurations.
How to Use This Guide
Use this deterministic sequence:
1. Choose template type (step, job, stage, or variables) based on repetition level. 2. Start from the corresponding example in this file. 3. Keep parameters explicit, typed, and minimally scoped. 4. Add runtime condition logic at stage/job level for branch or environment gating. 5. Validate the final root pipeline and template files.
Fallback behavior:
- If template complexity adds more risk than value, inline the logic in the root pipeline and note the reason.
- If shared template repositories are not available, use local
templates/files and keep paths explicit. - If template expression behavior is uncertain, use simpler runtime conditions and document assumptions.
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.yml2. 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 }}'
${{ if gt(length(parameters.dependsOn), 0) }}:
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'
- template: templates/deploy-stage.yml
parameters:
environment: 'production'
dependsOn:
- Deploy_staging4. 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: productionTemplates 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: myappTemplates from Different Branches
resources:
repositories:
- repository: templates
type: git
name: MyProject/Templates
ref: refs/heads/v2
jobs:
- template: ci-job.yml@templatesAdvanced 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 testConditional 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: productionTemplate 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
# approvalReminder: Show informational reminder to configure environment approvals/checks
parameters:
- name: environment
type: string
displayName: 'Target Environment'
- name: version
type: string
default: '$(Build.BuildId)'
displayName: 'Application Version'
- name: approvalReminder
type: boolean
default: true
displayName: 'Show Approval Reminder'Environment approvals/checks are enforced in Azure DevOps Environment settings, not by a YAML boolean parameter.
2. Default Values
Always provide sensible defaults for parameters.
parameters:
- name: buildConfiguration
type: string
default: 'Release'
- name: runTests
type: boolean
default: true3. Template Validation
Use parameter restrictions to validate inputs.
parameters:
- name: environment
type: string
values:
- dev
- staging
- production4. 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.yml5. 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 tagCommon 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 }}
$(Build.SourceVersion)
- task: Docker@2
displayName: 'Push Docker image'
inputs:
command: 'push'
repository: ${{ parameters.imageName }}
tags: |
${{ parameters.imageTag }}
$(Build.SourceVersion)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: 1. Use parameters for flexibility 2. Provide sensible defaults 3. Document your templates 4. Organize templates logically 5. Version your template repositories 6. Test templates thoroughly
Done Criteria
Template design is complete when:
- Selected template type matches the requested reuse scope.
- Parameters are typed and defaults are explicit.
- Branch/environment gating is implemented with deterministic runtime conditions.
- Root pipeline and template composition can be validated without path ambiguity.
Further Reading
Azure Pipelines YAML Schema Reference
This document provides a comprehensive reference for Azure Pipelines YAML syntax and structure.
How to Use This Reference
Use this deterministic sequence:
1. Confirm root structure (steps-only, jobs-based, or stages-based pipeline). 2. Add required root keys (trigger, pr, pool, variables, resources) for the requested mode. 3. Build hierarchy in order: stages -> jobs -> steps. 4. Apply dependsOn, condition, and deployment environment settings. 5. Validate YAML and schema compliance.
Fallback behavior:
- If advanced expressions are uncertain, prefer simpler explicit conditions.
- If schema details are missing locally, use the official schema links in this file and document assumptions.
- If validation tooling is unavailable, run manual hierarchy and syntax sanity checks before delivery.
Pipeline Structure
Azure Pipelines follow a hierarchical structure:
Pipeline
└── Stages
└── Jobs
└── StepsBasic 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: trueresources
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 Linuxvariables
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.ymlparameters
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
- eastusStages
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 buildStage 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 buildDeployment 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: valueBash 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: 0Checkout Step
steps:
- checkout: self
clean: true
fetchDepth: 1
lfs: false
submodules: false
persistCredentials: falseDownload Step
steps:
- download: current
artifact: artifactName
patterns: '**/*.zip'
- download: upstream
artifact: artifactNamePublish 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 canceledCustom 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: valueTemplate 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 testService 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/testMatrix 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 testEnvironment 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
1. Use specific versions: Pin vmImage and task versions 2. Use displayName: Add clear display names for readability 3. Use stages: Organize complex pipelines with stages 4. Use templates: Create reusable templates for common patterns 5. Use conditions: Control execution flow with conditions 6. Use dependsOn: Optimize with explicit dependencies 7. Use environments: Track deployments with environments 8. Use parameters: Make pipelines configurable with runtime parameters 9. Cache dependencies: Use Cache task for package managers 10. Set timeouts: Prevent hung jobs with timeout settings
Official Documentation
Done Criteria
Schema usage is complete when:
- Pipeline hierarchy is internally consistent (
stages/jobs/stepsare not mixed incorrectly). - Runtime conditions and dependencies are explicit for non-trivial flow.
- Deployment stages use
deploymentjobs andenvironmentwhere applicable. - Validation output or documented manual fallback confirms structural correctness.
# Basic CI Pipeline
# Simple continuous integration pipeline for Node.js application
trigger:
branches:
include:
- main
- develop
paths:
exclude:
- docs/**
- README.md
pr:
branches:
include:
- main
pool:
vmImage: 'ubuntu-22.04'
variables:
nodeVersion: '20.x'
buildConfiguration: 'Release'
steps:
- task: NodeTool@0
displayName: 'Install Node.js'
inputs:
versionSpec: $(nodeVersion)
- 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'
- script: npm run lint
displayName: 'Run linter'
continueOnError: true
- script: npm run build
displayName: 'Build application'
- script: npm test -- --coverage
displayName: 'Run tests with coverage'
- task: PublishTestResults@2
condition: succeededOrFailed()
displayName: 'Publish test results'
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '**/test-results.xml'
failTaskOnFailedTests: true
- task: PublishCodeCoverageResults@1
condition: succeededOrFailed()
displayName: 'Publish code coverage'
inputs:
codeCoverageTool: 'Cobertura'
summaryFileLocation: '$(System.DefaultWorkingDirectory)/**/coverage/cobertura-coverage.xml'
- task: PublishPipelineArtifact@1
displayName: 'Publish build artifacts'
inputs:
targetPath: '$(Build.SourcesDirectory)/dist'
artifact: 'webapp'
publishLocation: 'pipeline'
# .NET CI/CD Pipeline
# Build, test, and deploy .NET application to Azure App Service
trigger:
branches:
include:
- main
- develop
variables:
buildConfiguration: 'Release'
dotnetVersion: '8.x'
vmImageName: 'ubuntu-22.04'
stages:
- stage: Build
displayName: 'Build .NET Application'
jobs:
- job: Build
displayName: 'Build Job'
pool:
vmImage: $(vmImageName)
steps:
- task: UseDotNet@2
displayName: 'Install .NET SDK $(dotnetVersion)'
inputs:
version: $(dotnetVersion)
packageType: 'sdk'
- task: Cache@2
displayName: 'Cache NuGet packages'
inputs:
key: 'nuget | "$(Agent.OS)" | **/packages.lock.json'
restoreKeys: |
nuget | "$(Agent.OS)"
path: $(Pipeline.Workspace)/.nuget/packages
- task: DotNetCoreCLI@2
displayName: 'Restore NuGet packages'
inputs:
command: 'restore'
projects: '**/*.csproj'
- task: DotNetCoreCLI@2
displayName: 'Build solution'
inputs:
command: 'build'
projects: '**/*.csproj'
arguments: '--configuration $(buildConfiguration) --no-restore'
- task: DotNetCoreCLI@2
displayName: 'Run unit tests'
inputs:
command: 'test'
projects: '**/*Tests/*.csproj'
arguments: '--configuration $(buildConfiguration) --no-build --collect:"XPlat Code Coverage"'
- task: PublishTestResults@2
condition: succeededOrFailed()
displayName: 'Publish test results'
inputs:
testResultsFormat: 'VSTest'
testResultsFiles: '**/*.trx'
mergeTestResults: true
failTaskOnFailedTests: true
- task: PublishCodeCoverageResults@1
condition: succeededOrFailed()
displayName: 'Publish code coverage'
inputs:
codeCoverageTool: 'Cobertura'
summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml'
- task: DotNetCoreCLI@2
displayName: 'Publish application'
inputs:
command: 'publish'
projects: '**/*.csproj'
arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory) --no-build'
zipAfterPublish: true
modifyOutputPath: false
- task: PublishPipelineArtifact@1
displayName: 'Publish artifacts'
inputs:
targetPath: '$(Build.ArtifactStagingDirectory)'
artifact: 'webapp'
publishLocation: 'pipeline'
- stage: DeployStaging
displayName: 'Deploy to Staging'
dependsOn: Build
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop'))
jobs:
- deployment: DeployStaging
displayName: 'Deploy to Azure App Service (Staging)'
pool:
vmImage: $(vmImageName)
environment: staging
timeoutInMinutes: 30
strategy:
runOnce:
deploy:
steps:
- download: current
artifact: webapp
- task: AzureWebApp@1
displayName: 'Deploy to Azure Web App'
inputs:
azureSubscription: 'AzureServiceConnection'
appType: 'webAppLinux'
appName: 'myapp-staging'
package: '$(Pipeline.Workspace)/webapp/**/*.zip'
runtimeStack: 'DOTNETCORE|8.0'
- stage: DeployProduction
displayName: 'Deploy to Production'
dependsOn: Build
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: DeployProduction
displayName: 'Deploy to Azure App Service (Production)'
pool:
vmImage: $(vmImageName)
environment: production
timeoutInMinutes: 30
strategy:
runOnce:
deploy:
steps:
- download: current
artifact: webapp
- task: AzureWebApp@1
displayName: 'Deploy to Azure Web App'
inputs:
azureSubscription: 'AzureServiceConnection'
appType: 'webAppLinux'
appName: 'myapp-prod'
package: '$(Pipeline.Workspace)/webapp/**/*.zip'
runtimeStack: 'DOTNETCORE|8.0'
# Go Application CI/CD Pipeline
# Complete pipeline for building, testing, and deploying a Go microservice
# Includes: Matrix testing, Docker build, and Kubernetes deployment
trigger:
branches:
include:
- main
- develop
paths:
exclude:
- docs/**
- README.md
- '*.md'
pr:
branches:
include:
- main
variables:
# Go configuration
goVersion: '1.22'
# Docker configuration
dockerRegistryServiceConnection: 'myACR'
imageRepository: 'go-app'
containerRegistry: 'myregistry.azurecr.io'
dockerfilePath: '$(Build.SourcesDirectory)/Dockerfile'
tag: '$(Build.BuildId)'
# Kubernetes configuration
kubernetesServiceConnection: 'myAKSCluster'
# Common settings
vmImageName: 'ubuntu-22.04'
stages:
# ============================================================================
# Stage 1: Build and Test
# ============================================================================
- stage: BuildAndTest
displayName: 'Build and Test'
jobs:
# ----------------------------------------------------------------------
# Job: Matrix Testing across Go versions
# ----------------------------------------------------------------------
- job: TestMatrix
displayName: 'Test with Go Matrix'
pool:
vmImage: $(vmImageName)
timeoutInMinutes: 30
strategy:
matrix:
go121:
goTestVersion: '1.21'
go122:
goTestVersion: '1.22'
maxParallel: 2
steps:
# Install Go version
- task: GoTool@0
displayName: 'Install Go $(goTestVersion)'
inputs:
version: $(goTestVersion)
# Cache Go modules for faster builds
- task: Cache@2
displayName: 'Cache Go modules'
inputs:
key: 'go | "$(Agent.OS)" | go.sum'
restoreKeys: |
go | "$(Agent.OS)"
path: $(GOPATH)/pkg/mod
# Download dependencies
- script: go mod download
displayName: 'Download Go modules'
# Run static analysis
- script: go vet ./...
displayName: 'Run Go vet'
# Run tests with coverage and race detection
- script: |
go test -v -race -coverprofile=coverage.out -covermode=atomic ./... 2>&1 | tee test-output.txt
go-junit-report < test-output.txt > report.xml || true
displayName: 'Run Go tests with coverage'
# Generate HTML coverage report
- script: go tool cover -html=coverage.out -o coverage.html
displayName: 'Generate coverage report'
condition: succeededOrFailed()
# Publish test results
- task: PublishTestResults@2
condition: succeededOrFailed()
displayName: 'Publish test results'
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '**/report.xml'
failTaskOnFailedTests: true
testRunTitle: 'Go $(goTestVersion) Tests'
# Publish code coverage
- task: PublishCodeCoverageResults@1
condition: succeededOrFailed()
displayName: 'Publish code coverage'
inputs:
codeCoverageTool: 'Cobertura'
summaryFileLocation: '$(System.DefaultWorkingDirectory)/**/coverage.xml'
reportDirectory: '$(System.DefaultWorkingDirectory)/coverage'
# ----------------------------------------------------------------------
# Job: Build Go Binary
# ----------------------------------------------------------------------
- job: Build
displayName: 'Build Go Application'
dependsOn: TestMatrix
pool:
vmImage: $(vmImageName)
timeoutInMinutes: 20
steps:
# Install Go
- task: GoTool@0
displayName: 'Install Go $(goVersion)'
inputs:
version: $(goVersion)
# Cache Go modules
- task: Cache@2
displayName: 'Cache Go modules'
inputs:
key: 'go | "$(Agent.OS)" | go.sum'
restoreKeys: |
go | "$(Agent.OS)"
path: $(GOPATH)/pkg/mod
# Download dependencies
- script: go mod download
displayName: 'Download Go modules'
# Build for Linux (for Docker container)
- script: |
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-ldflags="-w -s -X main.Version=$(Build.BuildId) -X main.CommitHash=$(Build.SourceVersion)" \
-o $(Build.ArtifactStagingDirectory)/app \
./cmd/server
displayName: 'Build Go binary for Linux'
# Publish build artifacts
- task: PublishPipelineArtifact@1
displayName: 'Publish build artifacts'
inputs:
targetPath: '$(Build.ArtifactStagingDirectory)'
artifact: 'go-binary'
publishLocation: 'pipeline'
# ============================================================================
# Stage 2: Build and Push Docker Image
# ============================================================================
- stage: BuildDockerImage
displayName: 'Build Docker Image'
dependsOn: BuildAndTest
jobs:
- job: DockerBuild
displayName: 'Build and Push Docker Image'
pool:
vmImage: $(vmImageName)
timeoutInMinutes: 20
steps:
# Shallow clone for faster checkout
- checkout: self
fetchDepth: 1
# Download the built Go binary
- download: current
artifact: go-binary
displayName: 'Download Go binary'
# Docker@2: Build and push Docker images to a container registry
# Requires: Docker registry service connection
- task: Docker@2
displayName: 'Build and Push Docker image'
inputs:
command: buildAndPush
repository: $(imageRepository)
dockerfile: $(dockerfilePath)
containerRegistry: $(dockerRegistryServiceConnection)
tags: |
$(tag)
$(Build.SourceVersion)
# ============================================================================
# Stage 3: Deploy to Staging
# ============================================================================
- stage: DeployStaging
displayName: 'Deploy to Staging'
dependsOn: BuildDockerImage
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop'))
jobs:
- deployment: DeployStaging
displayName: 'Deploy to Staging Environment'
pool:
vmImage: $(vmImageName)
environment: staging
timeoutInMinutes: 30
strategy:
runOnce:
preDeploy:
steps:
- script: echo "Pre-deployment validation for staging"
displayName: 'Pre-deployment checks'
deploy:
steps:
- checkout: self
# KubernetesManifest@0: Deploy to Kubernetes cluster
# Requires: Kubernetes service connection
- task: KubernetesManifest@0
displayName: 'Deploy to Kubernetes (Staging)'
inputs:
action: 'deploy'
kubernetesServiceConnection: $(kubernetesServiceConnection)
namespace: 'staging'
manifests: |
$(Build.SourcesDirectory)/k8s/deployment.yml
$(Build.SourcesDirectory)/k8s/service.yml
containers: '$(containerRegistry)/$(imageRepository):$(tag)'
# Check rollout status
- task: Kubernetes@1
displayName: 'Check rollout status'
inputs:
connectionType: 'Kubernetes Service Connection'
kubernetesServiceEndpoint: $(kubernetesServiceConnection)
namespace: 'staging'
command: 'rollout'
arguments: 'status deployment/go-app --timeout=5m'
postDeploy:
steps:
- script: |
echo "Running smoke tests against staging"
curl -f https://staging.example.com/health || exit 1
displayName: 'Health check'
timeoutInMinutes: 5
continueOnError: true
on:
failure:
steps:
- script: echo "Staging deployment failed"
displayName: 'Failure notification'
success:
steps:
- script: echo "Staging deployment successful"
displayName: 'Success notification'
# ============================================================================
# Stage 4: Deploy to Production
# ============================================================================
- stage: DeployProduction
displayName: 'Deploy to Production'
dependsOn: BuildDockerImage
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: DeployProduction
displayName: 'Deploy to Production Environment'
pool:
vmImage: $(vmImageName)
environment: production
timeoutInMinutes: 30
strategy:
runOnce:
preDeploy:
steps:
- script: echo "Pre-deployment validation for production"
displayName: 'Pre-deployment checks'
deploy:
steps:
- checkout: self
# KubernetesManifest@0: Deploy to Kubernetes cluster
# Requires: Kubernetes service connection
- task: KubernetesManifest@0
displayName: 'Deploy to Kubernetes (Production)'
inputs:
action: 'deploy'
kubernetesServiceConnection: $(kubernetesServiceConnection)
namespace: 'production'
manifests: |
$(Build.SourcesDirectory)/k8s/deployment.yml
$(Build.SourcesDirectory)/k8s/service.yml
containers: '$(containerRegistry)/$(imageRepository):$(tag)'
# Check rollout status
- task: Kubernetes@1
displayName: 'Check rollout status'
inputs:
connectionType: 'Kubernetes Service Connection'
kubernetesServiceEndpoint: $(kubernetesServiceConnection)
namespace: 'production'
command: 'rollout'
arguments: 'status deployment/go-app --timeout=5m'
postDeploy:
steps:
- script: |
echo "Running production health check"
curl -f https://api.example.com/health || exit 1
displayName: 'Production health check'
timeoutInMinutes: 5
on:
failure:
steps:
- script: echo "Production deployment failed - initiating rollback notification"
displayName: 'Rollback on failure'
success:
steps:
- script: echo "Production deployment successful"
displayName: 'Deployment success notification'
# Kubernetes Deployment Pipeline
# Build Docker image and deploy to Kubernetes cluster
trigger:
branches:
include:
- main
variables:
dockerRegistryServiceConnection: 'myACR'
imageRepository: 'myapp'
containerRegistry: 'myregistry.azurecr.io'
dockerfilePath: '$(Build.SourcesDirectory)/Dockerfile'
tag: '$(Build.BuildId)'
vmImageName: 'ubuntu-22.04'
kubernetesServiceConnection: 'myK8sCluster'
namespace: 'production'
stages:
- stage: BuildAndPush
displayName: 'Build and Push Container Image'
jobs:
- job: Build
displayName: 'Build Docker Image'
pool:
vmImage: $(vmImageName)
steps:
- task: Docker@2
displayName: 'Build and Push image'
inputs:
command: buildAndPush
repository: $(imageRepository)
dockerfile: $(dockerfilePath)
containerRegistry: $(dockerRegistryServiceConnection)
tags: |
$(tag)
$(Build.SourceVersion)
- stage: DeployToK8s
displayName: 'Deploy to Kubernetes'
dependsOn: BuildAndPush
condition: succeeded()
jobs:
- deployment: DeployK8s
displayName: 'Deploy to AKS Cluster'
pool:
vmImage: $(vmImageName)
environment: kubernetes-production
timeoutInMinutes: 30
strategy:
runOnce:
deploy:
steps:
- checkout: self
- task: Kubernetes@1
displayName: 'Create namespace (if not exists)'
inputs:
connectionType: 'Kubernetes Service Connection'
kubernetesServiceEndpoint: $(kubernetesServiceConnection)
namespace: $(namespace)
command: 'create'
arguments: 'namespace $(namespace)'
continueOnError: true
- task: KubernetesManifest@0
displayName: 'Deploy to Kubernetes'
inputs:
action: 'deploy'
kubernetesServiceConnection: $(kubernetesServiceConnection)
namespace: $(namespace)
manifests: |
$(Build.SourcesDirectory)/k8s/deployment.yml
$(Build.SourcesDirectory)/k8s/service.yml
containers: '$(containerRegistry)/$(imageRepository):$(tag)'
- task: Kubernetes@1
displayName: 'Check rollout status'
inputs:
connectionType: 'Kubernetes Service Connection'
kubernetesServiceEndpoint: $(kubernetesServiceConnection)
namespace: $(namespace)
command: 'rollout'
arguments: 'status deployment/myapp --timeout=5m'
- task: Kubernetes@1
displayName: 'Get deployment info'
inputs:
connectionType: 'Kubernetes Service Connection'
kubernetesServiceEndpoint: $(kubernetesServiceConnection)
namespace: $(namespace)
command: 'get'
arguments: 'pods,svc,deployment'
- stage: SmokeTest
displayName: 'Smoke Test'
dependsOn: DeployToK8s
jobs:
- job: SmokeTest
displayName: 'Run Smoke Tests'
pool:
vmImage: $(vmImageName)
steps:
- script: |
echo "Running smoke tests against deployed application"
# Add actual smoke test commands here
curl -f https://myapp.example.com/health || exit 1
displayName: 'Health Check'
timeoutInMinutes: 5
- script: |
echo "All smoke tests passed"
displayName: 'Smoke Test Results'
# Multi-Stage CI/CD Pipeline
# Complete pipeline with build, test, and deployment stages
trigger:
branches:
include:
- main
- develop
- release/*
variables:
nodeVersion: '20.x'
buildConfiguration: 'Release'
stages:
- stage: Build
displayName: 'Build Stage'
jobs:
- job: BuildJob
displayName: 'Build Application'
pool:
vmImage: 'ubuntu-22.04'
steps:
- task: NodeTool@0
displayName: 'Install Node.js $(nodeVersion)'
inputs:
versionSpec: $(nodeVersion)
- 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'
- script: npm run build
displayName: 'Build application'
- task: CopyFiles@2
displayName: 'Copy build artifacts'
inputs:
SourceFolder: '$(Build.SourcesDirectory)/dist'
Contents: '**'
TargetFolder: '$(Build.ArtifactStagingDirectory)/dist'
- task: PublishPipelineArtifact@1
displayName: 'Publish build artifacts'
inputs:
targetPath: '$(Build.ArtifactStagingDirectory)'
artifact: 'drop'
publishLocation: 'pipeline'
- stage: Test
displayName: 'Test Stage'
dependsOn: Build
jobs:
- job: UnitTests
displayName: 'Run Unit Tests'
pool:
vmImage: 'ubuntu-22.04'
steps:
- task: NodeTool@0
displayName: 'Install Node.js $(nodeVersion)'
inputs:
versionSpec: $(nodeVersion)
- 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 test -- --coverage --ci
displayName: 'Run unit tests'
- task: PublishTestResults@2
condition: succeededOrFailed()
displayName: 'Publish test results'
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '**/junit.xml'
failTaskOnFailedTests: true
- task: PublishCodeCoverageResults@1
condition: succeededOrFailed()
displayName: 'Publish code coverage'
inputs:
codeCoverageTool: 'Cobertura'
summaryFileLocation: '$(System.DefaultWorkingDirectory)/**/coverage/cobertura-coverage.xml'
- job: LintJob
displayName: 'Run Linting'
pool:
vmImage: 'ubuntu-22.04'
steps:
- task: NodeTool@0
inputs:
versionSpec: $(nodeVersion)
- script: npm ci
displayName: 'Install dependencies'
- script: npm run lint
displayName: 'Run ESLint'
- stage: DeployStaging
displayName: 'Deploy to Staging'
dependsOn: Test
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop'))
jobs:
- deployment: DeployStaging
displayName: 'Deploy to Staging Environment'
pool:
vmImage: 'ubuntu-22.04'
environment: staging
timeoutInMinutes: 30
strategy:
runOnce:
deploy:
steps:
- download: current
artifact: drop
displayName: 'Download build artifacts'
- script: echo "Deploying to staging environment"
displayName: 'Deploy to Staging'
- script: echo "Running smoke tests on staging"
displayName: 'Smoke Test'
- stage: DeployProduction
displayName: 'Deploy to Production'
dependsOn:
- Test
- DeployStaging
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: DeployProduction
displayName: 'Deploy to Production Environment'
pool:
vmImage: 'ubuntu-22.04'
environment: production
timeoutInMinutes: 30
strategy:
runOnce:
preDeploy:
steps:
- script: echo "Pre-deployment checks"
displayName: 'Pre-Deployment Validation'
deploy:
steps:
- download: current
artifact: drop
displayName: 'Download build artifacts'
- script: echo "Deploying to production environment"
displayName: 'Deploy to Production'
postDeploy:
steps:
- script: echo "Post-deployment validation"
displayName: 'Post-Deployment Validation'
on:
failure:
steps:
- script: echo "Deployment failed - initiating rollback"
displayName: 'Rollback on Failure'
success:
steps:
- script: echo "Deployment successful"
displayName: 'Deployment Success Notification'
# Python CI/CD Pipeline
# Build, test, and deploy Python application
trigger:
branches:
include:
- main
- develop
variables:
pythonVersion: '3.11'
vmImageName: 'ubuntu-22.04'
stages:
- stage: Build
displayName: 'Build and Test'
jobs:
- job: BuildTest
displayName: 'Build and Test Python Application'
pool:
vmImage: $(vmImageName)
steps:
- task: UsePythonVersion@0
displayName: 'Use Python $(pythonVersion)'
inputs:
versionSpec: $(pythonVersion)
addToPath: true
architecture: 'x64'
- task: Cache@2
displayName: 'Cache pip packages'
inputs:
key: 'pip | "$(Agent.OS)" | requirements.txt'
restoreKeys: |
pip | "$(Agent.OS)"
path: $(Pipeline.Workspace)/.pip
- script: |
python -m pip install --upgrade pip
pip install --cache-dir $(Pipeline.Workspace)/.pip -r requirements.txt
displayName: 'Install dependencies'
- script: |
pip install pylint
pylint **/*.py --exit-zero
displayName: 'Run linting'
continueOnError: true
- script: |
pip install pytest pytest-cov
pytest tests/ --junitxml=junit/test-results.xml --cov=. --cov-report=xml --cov-report=html
displayName: 'Run tests with coverage'
- task: PublishTestResults@2
condition: succeededOrFailed()
displayName: 'Publish test results'
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '**/test-results.xml'
failTaskOnFailedTests: true
- task: PublishCodeCoverageResults@1
condition: succeededOrFailed()
displayName: 'Publish code coverage'
inputs:
codeCoverageTool: 'Cobertura'
summaryFileLocation: '$(System.DefaultWorkingDirectory)/**/coverage.xml'
reportDirectory: '$(System.DefaultWorkingDirectory)/**/htmlcov'
- script: |
pip install --upgrade build
python -m build
displayName: 'Build Python package'
- task: CopyFiles@2
displayName: 'Copy artifacts'
inputs:
SourceFolder: '$(Build.SourcesDirectory)'
Contents: |
**
!**/.git/**
!**/__pycache__/**
!**/tests/**
TargetFolder: '$(Build.ArtifactStagingDirectory)'
- task: PublishPipelineArtifact@1
displayName: 'Publish build artifacts'
inputs:
targetPath: '$(Build.ArtifactStagingDirectory)'
artifact: 'python-app'
publishLocation: 'pipeline'
- stage: DeployDev
displayName: 'Deploy to Development'
dependsOn: Build
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop'))
jobs:
- deployment: DeployDev
displayName: 'Deploy to Dev Environment'
pool:
vmImage: $(vmImageName)
environment: development
timeoutInMinutes: 30
strategy:
runOnce:
deploy:
steps:
- download: current
artifact: python-app
- task: UsePythonVersion@0
inputs:
versionSpec: $(pythonVersion)
- script: |
pip install -r $(Pipeline.Workspace)/python-app/requirements.txt
echo "Deploying Python application to development"
displayName: 'Deploy Application'
- stage: DeployProd
displayName: 'Deploy to Production'
dependsOn: Build
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: DeployProd
displayName: 'Deploy to Production Environment'
pool:
vmImage: $(vmImageName)
environment: production
timeoutInMinutes: 30
strategy:
runOnce:
deploy:
steps:
- download: current
artifact: python-app
- task: UsePythonVersion@0
inputs:
versionSpec: $(pythonVersion)
- script: |
pip install -r $(Pipeline.Workspace)/python-app/requirements.txt
echo "Deploying Python application to production"
displayName: 'Deploy Application'
# Pipeline Using Templates
# Demonstrates how to use reusable templates
trigger:
branches:
include:
- main
- develop
variables:
azureSubscription: 'AzureServiceConnection'
appNameBase: 'myapp'
stages:
# Build stage using template
- stage: Build
displayName: 'Build Application'
jobs:
- job: BuildApp
displayName: 'Build with Template'
pool:
vmImage: 'ubuntu-22.04'
steps:
# Use the reusable build template
- template: templates/build-template.yml
parameters:
nodeVersion: '20.x'
workingDirectory: '.'
runTests: true
publishArtifacts: true
# Deploy to staging using template
- template: templates/deploy-template.yml
parameters:
environment: 'staging'
stageCondition: 'succeeded()'
azureSubscription: $(azureSubscription)
appName: '$(appNameBase)-staging'
appType: 'webAppLinux'
artifactName: 'webapp'
approvalReminder: false
# Deploy to production using template (only from main branch)
- template: templates/deploy-template.yml
parameters:
environment: 'production'
dependsOn:
- Deploy_staging
stageCondition: "and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))"
azureSubscription: $(azureSubscription)
appName: '$(appNameBase)-prod'
appType: 'webAppLinux'
artifactName: 'webapp'
approvalReminder: true
# Reusable Build Template
# Template for building Node.js applications
parameters:
- name: nodeVersion
type: string
default: '20.x'
- name: workingDirectory
type: string
default: '.'
- name: runTests
type: boolean
default: true
- name: publishArtifacts
type: boolean
default: true
steps:
- task: NodeTool@0
displayName: 'Install Node.js ${{ parameters.nodeVersion }}'
inputs:
versionSpec: ${{ parameters.nodeVersion }}
- task: Cache@2
displayName: 'Cache npm packages'
inputs:
key: 'npm | "$(Agent.OS)" | ${{ parameters.workingDirectory }}/package-lock.json'
restoreKeys: |
npm | "$(Agent.OS)"
path: $(Pipeline.Workspace)/.npm
- script: npm ci --cache $(Pipeline.Workspace)/.npm
displayName: 'Install dependencies'
workingDirectory: ${{ parameters.workingDirectory }}
- script: npm run build
displayName: 'Build application'
workingDirectory: ${{ parameters.workingDirectory }}
- ${{ if eq(parameters.runTests, true) }}:
- script: npm test -- --coverage --ci
displayName: 'Run tests'
workingDirectory: ${{ parameters.workingDirectory }}
- task: PublishTestResults@2
condition: succeededOrFailed()
displayName: 'Publish test results'
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '**/junit.xml'
searchFolder: ${{ parameters.workingDirectory }}
- task: PublishCodeCoverageResults@1
condition: succeededOrFailed()
displayName: 'Publish code coverage'
inputs:
codeCoverageTool: 'Cobertura'
summaryFileLocation: '${{ parameters.workingDirectory }}/coverage/cobertura-coverage.xml'
- ${{ if eq(parameters.publishArtifacts, true) }}:
- task: PublishPipelineArtifact@1
displayName: 'Publish build artifacts'
inputs:
targetPath: '${{ parameters.workingDirectory }}/dist'
artifact: 'webapp'
publishLocation: 'pipeline'
# Reusable Deployment Template
# Template for deploying applications to Azure
parameters:
- name: environment
type: string
- name: dependsOn
type: object
default: []
- name: stageCondition
type: string
default: 'succeeded()'
- name: azureSubscription
type: string
- name: appName
type: string
- name: appType
type: string
default: 'webAppLinux'
- name: artifactName
type: string
default: 'webapp'
- name: approvalReminder
type: boolean
default: true
# Deprecated alias. Use approvalReminder instead.
- name: approvalRequired
type: boolean
default: false
stages:
- stage: Deploy_${{ parameters.environment }}
displayName: 'Deploy to ${{ parameters.environment }}'
${{ if gt(length(parameters.dependsOn), 0) }}:
dependsOn: ${{ parameters.dependsOn }}
condition: ${{ parameters.stageCondition }}
jobs:
- deployment: Deploy${{ parameters.environment }}
displayName: 'Deploy to ${{ parameters.environment }} Environment'
pool:
vmImage: 'ubuntu-22.04'
environment: ${{ parameters.environment }}
timeoutInMinutes: 30
strategy:
runOnce:
preDeploy:
steps:
- ${{ if or(eq(parameters.approvalReminder, true), eq(parameters.approvalRequired, true)) }}:
- script: |
echo "Reminder only: YAML does not enforce approvals."
echo "Configure Environment -> Approvals and checks in Azure DevOps for '${{ parameters.environment }}'."
displayName: 'Approval Reminder (Informational Only)'
- script: echo "Pre-deployment validation for ${{ parameters.environment }}"
displayName: 'Pre-Deployment Checks'
deploy:
steps:
- download: current
artifact: ${{ parameters.artifactName }}
displayName: 'Download artifacts'
- task: AzureWebApp@1
displayName: 'Deploy to Azure Web App'
inputs:
azureSubscription: ${{ parameters.azureSubscription }}
appType: ${{ parameters.appType }}
appName: ${{ parameters.appName }}
package: '$(Pipeline.Workspace)/${{ parameters.artifactName }}/**/*.zip'
postDeploy:
steps:
- script: |
echo "Post-deployment validation"
echo "Application deployed to: https://${{ parameters.appName }}.azurewebsites.net"
displayName: 'Post-Deployment Validation'
- script: |
# Health check
curl -f https://${{ parameters.appName }}.azurewebsites.net/health || exit 1
displayName: 'Health Check'
continueOnError: true
on:
failure:
steps:
- script: echo "Deployment to ${{ parameters.environment }} failed"
displayName: 'Failure Notification'
success:
steps:
- script: echo "Deployment to ${{ parameters.environment }} succeeded"
displayName: 'Success Notification'
#!/usr/bin/env python3
"""Regression tests for azure-pipelines-generator template safeguards."""
from pathlib import Path
import re
import unittest
SKILL_DIR = Path(__file__).resolve().parent.parent
DEPLOY_TEMPLATE = SKILL_DIR / "examples" / "templates" / "deploy-template.yml"
TEMPLATE_USAGE = SKILL_DIR / "examples" / "template-usage.yml"
class DeployTemplateRegressionTests(unittest.TestCase):
"""Ensure stage dependencies and approval reminder behavior stay safe."""
def test_depends_on_is_conditionally_inserted(self):
text = DEPLOY_TEMPLATE.read_text(encoding="utf-8")
self.assertIn(
"${{ if gt(length(parameters.dependsOn), 0) }}:",
text,
"dependsOn must be conditionally inserted when non-empty.",
)
self.assertNotRegex(
text,
r"(?m)^ {4}dependsOn:\s+\$\{\{\s*parameters\.dependsOn\s*\}\}\s*$",
"unconditional dependsOn reintroduces parallel-stage regression risk.",
)
def test_approval_reminder_is_explicit_and_backward_compatible(self):
text = DEPLOY_TEMPLATE.read_text(encoding="utf-8")
self.assertRegex(text, r"- name:\s+approvalReminder")
self.assertRegex(text, r"- name:\s+approvalRequired")
self.assertIn(
"Approval Reminder (Informational Only)",
text,
"approval reminder display text must explicitly indicate informational-only behavior.",
)
self.assertIn(
"YAML does not enforce approvals.",
text,
"template must clarify approvals are configured in environment checks.",
)
class TemplateUsageRegressionTests(unittest.TestCase):
"""Ensure template consumers use safe defaults and updated parameter names."""
def test_staging_uses_sequential_default_without_depends_on(self):
text = TEMPLATE_USAGE.read_text(encoding="utf-8")
match = re.search(
r"environment:\s*'staging'(?P<body>[\s\S]*?)\n\s*# Deploy to production",
text,
)
self.assertIsNotNone(match, "staging template usage block not found.")
body = match.group("body")
self.assertNotRegex(
body,
r"(?m)^\s*dependsOn:",
"staging block should omit dependsOn to use sequential stage default.",
)
self.assertRegex(body, r"approvalReminder:\s*false")
def test_production_keeps_explicit_dependency_and_new_parameter(self):
text = TEMPLATE_USAGE.read_text(encoding="utf-8")
match = re.search(r"environment:\s*'production'(?P<body>[\s\S]*)$", text)
self.assertIsNotNone(match, "production template usage block not found.")
body = match.group("body")
self.assertRegex(body, r"dependsOn:\s*\n\s*-\s*Deploy_staging")
self.assertRegex(body, r"approvalReminder:\s*true")
self.assertNotIn("approvalRequired:", text)
if __name__ == "__main__":
unittest.main()
Related skills
How it compares
Use azure-pipelines-generator for Azure DevOps YAML scaffolding; use a GitHub Actions skill when the repository runs on GitHub-hosted CI instead.
FAQ
What does azure-pipelines-generator produce?
azure-pipelines-generator produces Azure Pipelines YAML with build, test, and deploy stages. The output is intended for azure-pipelines.yml in Azure DevOps CI/CD projects.
Which platform does azure-pipelines-generator target?
azure-pipelines-generator targets Azure DevOps Azure Pipelines from akin-ozer/cc-devops-skills. It is meant for teams standardizing on Microsoft pipeline YAML rather than other CI providers.
When should teams use azure-pipelines-generator?
Teams should use azure-pipelines-generator when they need CI/CD quickly and want generated build, test, and deploy stage YAML instead of authoring every Azure Pipelines block manually.