managing-azure-devops

Author Azure DevOps YAML pipelines — multi-stage deploys, templates, variable groups, environment approvals.

SKILL.md

GitHub

Azure DevOps Pipelines Skill

Fast reference for Azure DevOps YAML pipelines. See REFERENCE.md for template libraries, service connections, container jobs, caching, and advanced patterns.


When to Use

Auto-Detection: azure-pipelines.yml in project root, .azuredevops/ directory, user mentions Azure DevOps.


CRITICAL: Azure DevOps vs GitHub Actions

Models frequently confuse these two systems. They have different syntax.

Concept Azure DevOps GitHub Actions (WRONG)
Built-in step task: TaskName@Version uses: action/name@v1
Script step script: or bash: run:
Trigger (push) trigger: on:
Trigger (PR) pr: on: pull_request
Variable (runtime) $(variableName) ${{ env.VAR }}
Variable (compile-time) ${{ parameters.x }} ${{ inputs.x }}
Variable (condition) $[variables.x] N/A
Artifacts upload PublishBuildArtifacts@1 actions/upload-artifact
Environment deploy deployment: job + environment: environment: on job

Variable Expression Syntax (THREE distinct syntaxes)

Syntax Evaluated At Use Case Example
${{ }} Compile time Template parameters, structural decisions ${{ parameters.env }}
$( ) Runtime Variables, secrets, predefined variables $(Build.SourceBranch)
$[ ] Runtime conditions Conditional expressions in YAML $[eq(variables['Build.Reason'], 'PullRequest')]

Multi-Stage Pipeline Structure

Pipelines use a stages > jobs > steps hierarchy:

trigger:
  branches:
    include: [main, release/*]
  paths:
    exclude: [docs/**, '*.md']

pr:
  branches:
    include: [main]

pool:
  vmImage: 'ubuntu-latest'

variables:
  - group: 'my-variable-group'
  - name: buildConfiguration
    value: 'Release'

stages:
  - stage: Build
    jobs:
      - job: BuildJob
        steps:
          - task: DotNetCoreCLI@2
            inputs:
              command: 'build'
              projects: '**/*.csproj'
              arguments: '--configuration $(buildConfiguration)'
          - task: PublishBuildArtifacts@1
            inputs:
              PathtoPublish: '$(Build.ArtifactStagingDirectory)'
              ArtifactName: 'drop'

  - stage: DeployStaging
    dependsOn: Build
    jobs:
      - deployment: DeployStaging
        environment: 'staging'
        strategy:
          runOnce:
            deploy:
              steps:
                - script: echo 'Deploying to staging...'

  - stage: DeployProduction
    dependsOn: DeployStaging
    jobs:
      - deployment: DeployProduction
        environment: 'production'
        strategy:
          runOnce:
            deploy:
              steps:
                - script: echo 'Deploying to production...'

Deployment Jobs

CRITICAL: Use deployment Not job for Environments

Deployment jobs REQUIRE a strategy: block. Regular job: does NOT support environment:.

# WRONG
- job: Deploy
    environment: 'production'    # Will NOT work

# CORRECT
- deployment: Deploy
    environment: 'production'
    strategy:                    # REQUIRED
      runOnce:
        deploy:
          steps:
            - script: echo deploy

Deployment Strategies

runOnce (most common): Single deployment with optional lifecycle hooks.

strategy:
  runOnce:
    preDeploy:
      steps: [...]
    deploy:
      steps: [...]
    on:
      failure:
        steps: [...]

rolling: Incremental deployment across targets.

strategy:
  rolling:
    maxParallel: 2
    deploy:
      steps: [...]

canary: Percentage-based rollout.

strategy:
  canary:
    increments: [10, 20]
    deploy:
      steps: [...]

Template References

Templates enable reusable pipeline components (step, job, stage, and variable templates).

Step Template

File: templates/build-steps.yml

parameters:
  - name: dotnetVersion
    type: string
    default: '8.x'

steps:
  - task: UseDotNet@2
    inputs:
      version: '${{ parameters.dotnetVersion }}'
  - task: DotNetCoreCLI@2
    inputs:
      command: 'build'

Usage: - template: templates/build-steps.yml

Stage Template

File: templates/deploy-stage.yml

parameters:
  - name: environment
    type: string
  - name: serviceConnection
    type: string
  - name: appName
    type: string

stages:
  - stage: Deploy_${{ parameters.environment }}
    jobs:
      - deployment: Deploy
        environment: '${{ parameters.environment }}'
        strategy:
          runOnce:
            deploy:
              steps:
                - task: AzureWebApp@1
                  inputs:
                    azureSubscription: '${{ parameters.serviceConnection }}'
                    appName: '${{ parameters.appName }}'

Usage:

- template: templates/deploy-stage.yml
  parameters:
    environment: 'production'
    serviceConnection: 'Azure-Prod'
    appName: 'myapp-prod'

Extends Template (Governance)

Forces all pipelines through a required base template:

# Pipeline file
extends:
  template: templates/pipeline-base.yml
  parameters:
    stages:
      - stage: Build
        jobs:
          - job: Build
            steps:
              - script: echo 'Building...'

Compile-Time vs Runtime in Templates

# ${{ }} - Compile time: structural decisions, template parameters
- ${{ if eq(parameters.includeTests, true) }}:
  - task: DotNetCoreCLI@2
    inputs:
      command: 'test'

# $() - Runtime: pipeline variables, secrets
- script: echo $(Build.BuildId)

See REFERENCE.md for complete template library patterns.


Variable Groups and Key Vault

variables:
  - group: 'my-app-variables'       # Variable group from UI
  - group: 'keyvault-secrets'       # Linked to Azure Key Vault
  - name: localVar
    value: 'inline-value'

AzureKeyVault Task (Inline Fetch)

- task: AzureKeyVault@2
  inputs:
    azureSubscription: 'my-service-connection'
    KeyVaultName: 'my-keyvault'
    SecretsFilter: 'db-password,api-key'
    RunAsPreJob: false

- script: echo "Secret length: ${#DB_PASSWORD}"
  env:
    DB_PASSWORD: $(db-password)

Environment Approvals

CRITICAL: Approvals Are Configured in the UI, NOT in YAML

The YAML just references the environment name. Approval gates are set on the Environment in Project Settings.

# YAML: just references the environment
- deployment: DeployProd
    environment: 'production'    # Approvals configured on this environment in UI

Where to configure (all in Azure DevOps UI):

  1. Pipelines > Environments > select environment > Approvals and checks
  2. Add: Approvals, Branch control, Business hours, Exclusive lock, Template validation

.NET Build and Test Tasks

# Install SDK
- task: UseDotNet@2
  inputs:
    packageType: 'sdk'
    version: '8.x'

# Restore
- task: DotNetCoreCLI@2
  inputs:
    command: 'restore'
    projects: '**/*.csproj'

# Build
- task: DotNetCoreCLI@2
  inputs:
    command: 'build'
    arguments: '--configuration $(buildConfiguration) --no-restore'

# Test with coverage
- task: DotNetCoreCLI@2
  inputs:
    command: 'test'
    projects: '**/*Tests.csproj'
    arguments: '--no-build --collect:"XPlat Code Coverage" --logger trx'

# Publish
- task: DotNetCoreCLI@2
  inputs:
    command: 'publish'
    publishWebProjects: true
    arguments: '--output $(Build.ArtifactStagingDirectory)'
    zipAfterPublish: true

Artifact Publishing

# Pipeline Artifacts (preferred)
- task: PublishPipelineArtifact@1
  inputs:
    targetPath: '$(Build.ArtifactStagingDirectory)'
    artifact: 'drop'

- task: DownloadPipelineArtifact@2
  inputs:
    artifactName: 'drop'
    targetPath: '$(Pipeline.Workspace)/drop'

# Build Artifacts (legacy)
- task: PublishBuildArtifacts@1
  inputs:
    PathtoPublish: '$(Build.ArtifactStagingDirectory)'
    ArtifactName: 'drop'

- task: DownloadBuildArtifacts@1
  inputs:
    buildType: 'current'
    artifactName: 'drop'

Common Patterns

Conditions

# Stage: deploy only from main
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))

# Step: run only on PRs
condition: eq(variables['Build.Reason'], 'PullRequest')

# Step: run only on manual trigger
condition: eq(variables['Build.Reason'], 'Manual')

Scheduled and Manual Triggers

trigger: none    # Disable CI trigger

schedules:
  - cron: '0 2 * * *'
    displayName: 'Nightly build'
    branches:
      include: [main]
    always: true

parameters:
  - name: environment
    type: string
    default: 'staging'
    values: [staging, production]

Predefined Variables

Variable Description
$(Build.BuildId) Unique build ID
$(Build.SourceBranch) Full branch ref (refs/heads/main)
$(Build.SourceBranchName) Short branch name (main)
$(Build.SourceVersion) Commit SHA
$(Build.Reason) IndividualCI, PullRequest, Manual, Schedule
$(Build.ArtifactStagingDirectory) Staging directory for artifacts
$(Pipeline.Workspace) Workspace root

Quick Reference Card

# --- Triggers ---
trigger:
  branches:
    include: [main]
pr:
  branches:
    include: [main]

# --- Variables ---
variables:
  - group: 'my-variable-group'
  - name: myVar
    value: 'my-value'

# --- Build Stage ---
stages:
  - stage: Build
    jobs:
      - job: Build
        pool:
          vmImage: 'ubuntu-latest'
        steps:
          - task: UseDotNet@2           # task: not uses:
            inputs:
              version: '8.x'
          - script: dotnet build        # script: not run:

# --- Deploy Stage ---
  - stage: Deploy
    jobs:
      - deployment: Deploy              # NOT job:
        environment: 'production'       # Approvals in UI
        strategy:                       # REQUIRED
          runOnce:
            deploy:
              steps:
                - script: echo deploy

# --- Templates ---
  - template: templates/steps.yml
    parameters:
      env: 'production'

# --- Key Vault ---
  - task: AzureKeyVault@2
    inputs:
      azureSubscription: 'my-conn'
      KeyVaultName: 'my-vault'
      SecretsFilter: 'secret1,secret2'

# --- Variable Syntax ---
# ${{ parameters.x }}  --> compile-time (templates)
# $(variableName)      --> runtime (variables, secrets)
# $[variables.x]       --> runtime conditions

See REFERENCE.md for: Template library patterns, service connections and managed identity, container jobs and self-hosted agents, pipeline caching, release pipelines vs YAML pipelines, Azure CLI and script tasks, and advanced patterns.