Azure DevOps is Microsoft’s suite for planning, developing, testing, and deploying software. It includes Azure Repos, Pipelines, Artifacts, Test Plans, and Boards — integrated with GitHub, Azure services, and third-party tools. For Azure-centric teams, it provides native service connections, environment gates, and deployment tracking.

Azure DevOps Services

Service Purpose
Azure Repos Git repositories with pull requests and branch policies
Azure Pipelines CI/CD for any platform, language, and cloud
Azure Artifacts Package feeds (npm, NuGet, Maven, Python, Universal)
Azure Boards Work tracking, sprints, backlogs, Kanban
Azure Test Plans Manual, exploratory, and automated testing

Project Setup

  1. Create an organization at dev.azure.com
  2. Create a project with Git version control and Agile process template
  3. Connect your repository or import from GitHub
  4. Configure branch policies on main:
    • Require pull request with minimum 1 reviewer
    • Require build validation (CI pipeline must pass)
    • Require linked work items for traceability
  # Install Azure DevOps CLI extension
az extension add --name azure-devops

# Configure defaults
az devops configure --defaults organization=https://dev.azure.com/myorg project=web-app

# List pipelines
az pipelines list --output table
  

CI Pipeline (YAML)

Create azure-pipelines.yml in your repo root:

  trigger:
  branches:
    include:
      - main
      - release/*

pr:
  branches:
    include:
      - main

pool:
  vmImage: 'ubuntu-latest'

variables:
  nodeVersion: '20.x'
  buildConfiguration: 'Release'

stages:
  - stage: Build
    jobs:
      - job: BuildAndTest
        steps:
          - task: NodeTool@0
            inputs:
              versionSpec: $(nodeVersion)

          - script: npm ci
            displayName: 'Install dependencies'

          - script: npm run lint
            displayName: 'Lint'

          - script: npm test -- --coverage
            displayName: 'Run tests with coverage'

          - script: npm run build
            displayName: 'Build application'

          - task: PublishTestResults@2
            inputs:
              testResultsFormat: 'JUnit'
              testResultsFiles: '**/test-results.xml'

          - task: PublishCodeCoverageResults@2
            inputs:
              summaryFileLocation: '$(System.DefaultWorkingDirectory)/coverage/cobertura-coverage.xml'

          - task: PublishBuildArtifacts@1
            inputs:
              pathToPublish: 'dist'
              artifactName: 'drop'
  

CD with Environments and Approvals

Multi-stage YAML pipelines with environment approvals gate production deploys:

    - stage: DeployStaging
    dependsOn: Build
    jobs:
      - deployment: DeployWebStaging
        environment: staging
        strategy:
          runOnce:
            deploy:
              steps:
                - task: AzureWebApp@1
                  inputs:
                    azureSubscription: 'Azure-Prod-ServiceConnection'
                    appType: 'webAppLinux'
                    appName: 'my-webapp-staging'
                    package: '$(Pipeline.Workspace)/drop'

  - stage: DeployProduction
    dependsOn: DeployStaging
    jobs:
      - deployment: DeployWebProduction
        environment: production    # Requires manual approval
        strategy:
          runOnce:
            deploy:
              steps:
                - task: AzureWebApp@1
                  inputs:
                    azureSubscription: 'Azure-Prod-ServiceConnection'
                    appType: 'webAppLinux'
                    appName: 'my-webapp-prod'
                    package: '$(Pipeline.Workspace)/drop'
                    deployToSlotOrASE: true
                    resourceGroupName: 'rg-webapp-prod'
                    slotName: 'staging'

                - task: AzureAppServiceManage@0
                  inputs:
                    azureSubscription: 'Azure-Prod-ServiceConnection'
                    action: 'Swap Slots'
                    webAppName: 'my-webapp-prod'
                    resourceGroupName: 'rg-webapp-prod'
                    sourceSlot: 'staging'
  

Infrastructure as Code Pipeline

Deploy Azure resources with Bicep in the same pipeline:

    - stage: DeployInfra
    jobs:
      - job: DeployBicep
        steps:
          - task: AzureCLI@2
            inputs:
              azureSubscription: 'Azure-Prod-ServiceConnection'
              scriptType: 'bash'
              scriptLocation: 'inlineScript'
              inlineScript: |
                az deployment group what-if \
                  --resource-group rg-webapp-prod \
                  --template-file infra/main.bicep \
                  --parameters infra/parameters.prod.json

                az deployment group create \
                  --resource-group rg-webapp-prod \
                  --template-file infra/main.bicep \
                  --parameters infra/parameters.prod.json
  

Service Connections and Secrets

Create service connections under Project SettingsService connectionsAzure Resource Manager:

Scope Use Case
Subscription Broad access — avoid for production
Resource Group Scoped to rg-webapp-prod — recommended
Managed Identity Workload identity federation — most secure

Link Variable Groups to Azure Key Vault for secrets:

  variables:
  - group: prod-secrets    # Linked to Key Vault

steps:
  - script: echo "Deploying with secret from Key Vault"
    env:
      DB_PASSWORD: $(db-admin-password)
  

Real-World Scenario: Full CI/CD for Azure Web App

Stage Action
PR opened CI runs: lint, unit tests, SAST scan
Merge to main Build artifact; deploy to staging slot
Staging validation Smoke tests, integration tests against staging
Production gate Manual approval from release manager
Production deploy Swap staging → production (zero downtime)
Post-deploy Application Insights availability test verification

Azure DevOps vs GitHub Actions

Feature Azure DevOps GitHub Actions
Code hosting Azure Repos or GitHub GitHub only
Pipelines YAML, classic releases YAML workflows
Azure integration Native service connections OIDC federation
Work tracking Azure Boards (built-in) GitHub Issues/Projects
Package feeds Azure Artifacts GitHub Packages
Best for Enterprise Azure teams GitHub-centric teams

Both are production-ready. Many teams use GitHub for code and Azure DevOps for pipelines and boards.

Common Mistakes

  1. Secrets in pipeline YAML — use Variable Groups linked to Key Vault
  2. No branch policies — direct pushes to main bypass CI validation
  3. Overly broad service connection — scope to resource group, not subscription
  4. Missing deployment gates — auto-deploy to production without approval
  5. No pipeline caching — every build downloads all dependencies from scratch
  6. Ignoring pipeline failures in PRs — merge broken code and debug in production

Troubleshooting

Issue Diagnosis Fix
Pipeline not triggering Branch filter mismatch Verify trigger/pr branch includes match
Service connection auth failure Expired secret or wrong scope Regenerate SP; verify RBAC on resource group
Deployment succeeds but app broken Wrong artifact or config Check deployment logs; verify app settings
Slow builds No caching, large artifacts Add npm/pip cache; use multi-stage builds
Environment approval stuck No approvers configured Add approvers in Environment settings
  # Run pipeline manually
az pipelines run --name "web-app-ci" --branch main

# List recent runs
az pipelines runs list --pipeline-ids 1 --top 5 -o table
  

Best Practices

Practice Benefit
YAML pipelines in repo Version-controlled, reviewable CI/CD
Environment approvals Gate production deploys with audit trail
Variable groups + Key Vault Secure secrets, no plaintext in YAML
Pipeline caching Faster builds (npm, NuGet, Docker layers)
Deployment jobs (not regular jobs) Deployment history and rollback tracking
Parallel test stages Reduce total pipeline duration
Artifact immutability Same build artifact promoted through all stages

Next: Azure Container Instances and Container Apps.