DEV Community

Cover image for Azure DevOps: Microsoft's Platform for Repos, Pipelines, Boards, and Deployments
Rhuturaj Takle
Rhuturaj Takle

Posted on

Azure DevOps: Microsoft's Platform for Repos, Pipelines, Boards, and Deployments

Azure DevOps: Microsoft's Platform for Repos, Pipelines, Boards, and Deployments

A practical guide to Azure DevOps — Microsoft's integrated platform spanning source control, CI/CD pipelines, work item tracking, package management, and test planning, covering each service, YAML pipeline authoring, release strategies, and how it compares to GitHub Actions.


Table of Contents

  1. Introduction
  2. The Five Services
  3. Azure Repos
  4. Azure Pipelines: YAML Fundamentals
  5. A Complete .NET CI/CD Pipeline
  6. Variables, Variable Groups, and Secrets
  7. Multi-Stage Pipelines and Environments
  8. Templates and Reusability
  9. Azure Boards
  10. Azure Artifacts
  11. Azure Test Plans
  12. Self-Hosted Agents
  13. Azure DevOps vs. GitHub Actions/GitHub
  14. Quick Reference Table
  15. Conclusion

Introduction

Azure DevOps is Microsoft's integrated suite for the full software delivery lifecycle — source control, build/release pipelines, work tracking, package feeds, and manual/exploratory test management, all under one platform with a shared permission model and a long history predating (and still commonly used alongside or instead of) GitHub Actions. Organizations that adopted it in its earlier incarnation as Team Foundation Server (TFS) often still run substantial parts of their delivery pipeline on it today, and it remains a common choice for enterprises wanting an integrated, Microsoft-native alternative that combines project management (Boards) with CI/CD in a single product.

trigger:
  branches:
    include: [main]

pool:
  vmImage: 'ubuntu-latest'

steps:
  - task: UseDotNet@2
    inputs:
      version: '9.0.x'
  - script: dotnet restore
  - script: dotnet build --configuration Release
  - script: dotnet test --configuration Release
Enter fullscreen mode Exit fullscreen mode

That YAML pipeline definition — conceptually similar to a GitHub Actions workflow, but with Azure DevOps' own task-based syntax — is the modern, version-controlled way to define builds and releases in Azure DevOps, replacing the classic drag-and-drop pipeline editor that was the platform's original interface.


1. The Five Services

Azure DevOps is organized into five distinct but interconnected services, each independently usable:

Service Purpose
Azure Repos Git (or legacy TFVC) source control repositories
Azure Pipelines CI/CD build and release automation
Azure Boards Work item tracking — backlogs, sprints, Kanban boards
Azure Artifacts Package feeds (NuGet, npm, Maven, Python, universal packages)
Azure Test Plans Manual and exploratory test case management

A team can use all five together as a unified toolchain, or use only the pieces they need — a common pattern is using GitHub for source control while still using Azure Pipelines for CI/CD (and vice versa), since Azure Pipelines can build from a GitHub repository directly, and Azure Boards work items can link to GitHub commits and pull requests.


2. Azure Repos

Git repositories with familiar workflows

Azure Repos provides standard Git hosting — branches, pull requests, branch policies — functionally similar to GitHub or GitLab's core repository features, accessible via any Git client or IDE with Azure DevOps integration (Visual Studio has particularly deep native support).

Branch policies

Branch policies for `main`:
  ✓ Require a minimum number of reviewers (e.g., 2)
  ✓ Check for linked work items
  ✓ Require successful build validation before completing a PR
  ✓ Require comment resolution before merging
Enter fullscreen mode Exit fullscreen mode

Branch policies enforce quality gates directly at the branch level — a pull request targeting a protected branch can be configured to require passing builds, a minimum reviewer count, and linked work items before it's allowed to merge, similar in spirit to GitHub's branch protection rules, with Azure DevOps' own particular set of configurable checks.

Pull requests

PR #142: Add discount calculation to checkout
  Linked work item: #4521 (User Story: Support percentage discounts)
  Reviewers: 2 required, 1 approved
  Build validation: ✓ passed
  Status: Ready to complete
Enter fullscreen mode Exit fullscreen mode

Azure Repos pull requests support inline comments, required reviewers, automatic build validation triggers, and (distinctively) tight integration with Azure Boards work items — a PR can be directly linked to the user story or bug it addresses, and completing the PR can automatically transition that work item's state.


3. Azure Pipelines: YAML Fundamentals

Core structure

trigger:
  branches:
    include: [main, develop]

pr:
  branches:
    include: [main]

pool:
  vmImage: 'ubuntu-latest'

variables:
  buildConfiguration: 'Release'

steps:
  - task: UseDotNet@2
    displayName: 'Install .NET SDK'
    inputs:
      version: '9.0.x'

  - script: dotnet restore
    displayName: 'Restore dependencies'

  - script: dotnet build --configuration $(buildConfiguration)
    displayName: 'Build'

  - script: dotnet test --configuration $(buildConfiguration) --logger trx --results-directory $(Agent.TempDirectory)
    displayName: 'Run tests'

  - task: PublishTestResults@2
    inputs:
      testResultsFormat: 'VSTest'
      testResultsFiles: '**/*.trx'
Enter fullscreen mode Exit fullscreen mode
  • trigger — defines which branch pushes trigger a CI run (conceptually equivalent to GitHub Actions' on: push).
  • pr — defines pull-request-triggered validation, separate from the CI trigger.
  • pool — specifies which agent pool/VM image runs the pipeline (Microsoft-hosted or self-hosted, see Section 11).
  • task — a reusable, versioned unit of pipeline logic (like UseDotNet@2), analogous to a GitHub Action.
  • script — a raw shell command, the simplest way to run something without a dedicated task.

Tasks vs. scripts

- task: DotNetCoreCLI@2
  displayName: 'Build with the DotNetCoreCLI task'
  inputs:
    command: 'build'
    arguments: '--configuration Release'

- script: dotnet build --configuration Release
  displayName: 'Or just run it directly as a script'
Enter fullscreen mode Exit fullscreen mode

Both approaches are common and valid — dedicated tasks (like DotNetCoreCLI@2) often provide richer input validation, cross-platform handling, and structured logging integration than an equivalent raw script, but a plain script step is simpler and more transparent for straightforward commands, mirroring the same "official action vs. raw shell command" choice available in GitHub Actions.


4. A Complete .NET CI/CD Pipeline

trigger:
  branches:
    include: [main]

pool:
  vmImage: 'ubuntu-latest'

variables:
  buildConfiguration: 'Release'

stages:
  - stage: Build
    jobs:
      - job: BuildAndTest
        steps:
          - task: UseDotNet@2
            inputs:
              version: '9.0.x'

          - script: dotnet restore
            displayName: 'Restore'

          - script: dotnet build --configuration $(buildConfiguration) --no-restore
            displayName: 'Build'

          - script: dotnet test --configuration $(buildConfiguration) --no-build --logger trx
            displayName: 'Test'

          - task: PublishTestResults@2
            condition: always()
            inputs:
              testResultsFormat: 'VSTest'
              testResultsFiles: '**/*.trx'

          - script: dotnet publish -c $(buildConfiguration) -o $(Build.ArtifactStagingDirectory)
            displayName: 'Publish'

          - task: PublishBuildArtifacts@1
            inputs:
              pathToPublish: '$(Build.ArtifactStagingDirectory)'
              artifactName: 'drop'

  - stage: DeployStaging
    dependsOn: Build
    condition: succeeded()
    jobs:
      - deployment: DeployToStaging
        environment: 'staging'
        strategy:
          runOnce:
            deploy:
              steps:
                - download: current
                  artifact: drop
                - task: AzureWebApp@1
                  inputs:
                    azureSubscription: 'my-service-connection'
                    appName: 'my-api-staging'
                    package: '$(Pipeline.Workspace)/drop/**/*.zip'
Enter fullscreen mode Exit fullscreen mode

This mirrors the same build-then-deploy shape covered in this series' GitHub Actions guide: a Build stage that compiles, tests, and publishes an artifact, followed by a DeployStaging stage that only runs if Build succeeds, deploying the published artifact to an Azure Web App via a deployment job targeting a named environment (Section 6).


5. Variables, Variable Groups, and Secrets

Pipeline variables

variables:
  buildConfiguration: 'Release'
  dotnetVersion: '9.0.x'
Enter fullscreen mode Exit fullscreen mode
- script: echo "Building with $(buildConfiguration) configuration"
Enter fullscreen mode Exit fullscreen mode

Variables defined directly in the YAML are the simplest form — referenced with $(variableName) syntax throughout the pipeline.

Variable groups: shared, centrally-managed configuration

variables:
  - group: 'production-config'
Enter fullscreen mode Exit fullscreen mode

A variable group (managed under Pipelines → Library) centralizes variables shared across multiple pipelines — a connection string, a service URL, feature flag defaults — so they're maintained in one place rather than duplicated across every pipeline file that needs them, and can be scoped with their own permissions independent of the pipeline YAML itself.

Secret variables

variables:
  - group: 'production-secrets'   # variables marked as "secret" in the Library UI are automatically masked in logs
Enter fullscreen mode Exit fullscreen mode

Secrets are marked as such in the Azure DevOps UI (not stored in plain text in the YAML file itself) — their values are automatically redacted from pipeline logs, similar to GitHub Actions' secret masking behavior, and can be sourced from Azure DevOps' own encrypted storage or linked directly to an Azure Key Vault for centralized secret management across both the pipeline and the applications it deploys.

Linking directly to Azure Key Vault

- task: AzureKeyVault@2
  inputs:
    azureSubscription: 'my-service-connection'
    KeyVaultName: 'my-keyvault'
    SecretsFilter: '*'
Enter fullscreen mode Exit fullscreen mode

This task pulls secrets directly from an Azure Key Vault at pipeline run-time, making them available as pipeline variables for that run — avoiding duplicating secret values between Key Vault (where the application likely also sources them) and Azure DevOps' own variable storage.


6. Multi-Stage Pipelines and Environments

Stages: sequencing build and multiple deployment targets

stages:
  - stage: Build
    jobs: [ /* ... */ ]

  - stage: DeployStaging
    dependsOn: Build
    jobs: [ /* ... */ ]

  - stage: DeployProduction
    dependsOn: DeployStaging
    condition: succeeded()
    jobs: [ /* ... */ ]
Enter fullscreen mode Exit fullscreen mode

Stages are Azure Pipelines' top-level sequencing construct — conceptually similar to a chain of jobs across separate GitHub Actions workflows, but expressed within a single pipeline file, with dependsOn controlling execution order and condition controlling whether a stage proceeds.

Environments and approval gates

- stage: DeployProduction
  jobs:
    - deployment: DeployToProduction
      environment: 'production'   # references an Environment configured with its own approval checks
      strategy:
        runOnce:
          deploy:
            steps:
              - task: AzureWebApp@1
                inputs:
                  appName: 'my-api-prod'
Enter fullscreen mode Exit fullscreen mode

An Azure DevOps Environment (configured under Pipelines → Environments) can require manual approval from designated approvers, restrict which branches may deploy to it, and enforce business-hours deployment windows — directly analogous to GitHub's environment protection rules covered in this series' GitHub Actions guide, giving a genuine human gate on production deployments without a separate external approval tool.

Deployment strategies

strategy:
  runOnce:
    deploy:
      steps: [ /* ... */ ]

# or, for a more gradual rollout:
strategy:
  canary:
    increments: [10, 20, 70]
    deploy:
      steps: [ /* ... */ ]
Enter fullscreen mode Exit fullscreen mode

Azure Pipelines' deployment jobs support built-in strategies beyond a simple runOnce deploy — canary deployments (ramping traffic to the new version in increments) and rolling deployments are available as first-class YAML constructs, rather than needing to hand-roll the incremental rollout logic yourself.


7. Templates and Reusability

Step templates

# templates/build-steps.yml
parameters:
  - name: dotnetVersion
    type: string
    default: '9.0.x'

steps:
  - task: UseDotNet@2
    inputs:
      version: ${{ parameters.dotnetVersion }}
  - script: dotnet restore
  - script: dotnet build --configuration Release
Enter fullscreen mode Exit fullscreen mode
# azure-pipelines.yml
steps:
  - template: templates/build-steps.yml
    parameters:
      dotnetVersion: '9.0.x'
Enter fullscreen mode Exit fullscreen mode

Templates let you extract a reusable sequence of steps (or entire jobs/stages) into a separate file, parameterized and referenced from multiple pipeline definitions — the direct equivalent of GitHub Actions' composite actions and reusable workflows, serving the same goal of avoiding copy-pasted, slowly-diverging pipeline logic across many repositories.

Extending a base template

# templates/base-pipeline.yml
parameters:
  - name: environment
    type: string

jobs:
  - deployment: Deploy
    environment: ${{ parameters.environment }}
    strategy:
      runOnce:
        deploy:
          steps:
            - script: echo "Deploying to ${{ parameters.environment }}"
Enter fullscreen mode Exit fullscreen mode
# azure-pipelines.yml
extends:
  template: templates/base-pipeline.yml
  parameters:
    environment: 'production'
Enter fullscreen mode Exit fullscreen mode

extends goes a step further than a plain template reference, letting an organization define a mandatory base pipeline structure (often used to enforce required security scanning steps or compliance checks across every team's pipeline) that individual repositories customize only within the parameters the base template explicitly allows — a governance mechanism GitHub Actions' reusable workflows don't have a direct equivalent for.


8. Azure Boards

Work item types and hierarchy

Epic: Q3 Checkout Redesign
  └── Feature: Support percentage-based discounts
        └── User Story: As a customer, I can apply a discount code at checkout
              └── Task: Implement discount calculation logic
              └── Task: Add discount code input field to checkout UI
              └── Bug: Discount not applied when quantity > 10
Enter fullscreen mode Exit fullscreen mode

Azure Boards organizes work into a configurable hierarchy — typically Epics, Features, User Stories/Product Backlog Items, Tasks, and Bugs — supporting both Scrum and Kanban-style process templates (Basic, Agile, Scrum, CMMI), selectable per project based on the team's preferred methodology.

Backlogs and sprints

Sprint 24 (Jul 14 - Jul 28)
  Committed: 32 story points
  ├── #4521 - Support percentage discounts (5 pts) - In Progress
  ├── #4530 - Fix checkout validation bug (2 pts) - Done
  └── #4535 - Add discount analytics dashboard (8 pts) - To Do
Enter fullscreen mode Exit fullscreen mode

Sprints (iterations) organize work into time-boxed periods with capacity planning, burndown charts, and velocity tracking — the core Scrum ceremony support that's historically been one of Azure Boards' strongest differentiators against a pure CI/CD-focused tool.

Linking work items to code

Commit message: "Fix discount calculation #4521"
Enter fullscreen mode Exit fullscreen mode

Referencing a work item ID in a commit message, branch name, or pull request automatically creates a traceable link — from a work item, you can see every commit, branch, build, and pull request associated with it, giving genuine end-to-end traceability from a business requirement through to the exact code and deployment that fulfilled it, a workflow that requires more manual stitching-together (via GitHub Issues + separate linking conventions) in a pure GitHub-based setup.

Kanban boards

To Do | In Progress | Code Review | Testing | Done
 #4535 | #4521        | #4530        |          | #4519
Enter fullscreen mode Exit fullscreen mode

A visual, drag-and-drop board view of the current sprint or backlog, with configurable columns matching a team's actual workflow stages — functionally similar to a GitHub Projects board, with deeper native integration into the rest of Azure Boards' work item hierarchy and reporting.


9. Azure Artifacts

# Publishing a NuGet package to an Azure Artifacts feed
dotnet nuget push MyLibrary.1.0.0.nupkg --source "https://pkgs.dev.azure.com/myorg/_packaging/myfeed/nuget/v3/index.json" --api-key az
Enter fullscreen mode Exit fullscreen mode
<!-- Consuming from the feed in another project -->
<PackageSource key="myfeed" value="https://pkgs.dev.azure.com/myorg/_packaging/myfeed/nuget/v3/index.json" />
Enter fullscreen mode Exit fullscreen mode

Azure Artifacts hosts private package feeds — NuGet, npm, Maven, Python (pip), and a generic "universal packages" format — letting an organization publish and consume internal packages (a shared internal library, say) without needing a separate package registry product. Feeds support upstream sources, letting a private feed also proxy and cache packages from the public NuGet.org/npmjs.com registries, giving a single feed URL that resolves both internal and public packages, with the added benefit of caching public packages against upstream outages or a removed/yanked public package version.


10. Azure Test Plans

Test Suite: Checkout Flow
  Test Case #201: Apply valid discount code
    Steps:
      1. Add item to cart → Expected: Item appears in cart
      2. Enter valid discount code → Expected: Discount applied, total updated
      3. Complete checkout → Expected: Order confirmed with discounted total
    Status: Passed (last run: 2026-07-15)

  Test Case #202: Apply expired discount code
    Status: Failed — bug #4530 filed
Enter fullscreen mode Exit fullscreen mode

Azure Test Plans manages structured manual and exploratory test cases — distinct from automated tests (which live in your actual codebase and run via Azure Pipelines) — giving QA teams a structured way to define test steps, track pass/fail status per test run, and automatically file a linked bug when a manual test fails. This is a genuinely distinctive piece of the Azure DevOps suite; GitHub's ecosystem has no direct first-party equivalent, relying instead on third-party test management tools or informal tracking via Issues for manual QA processes.

Linking automated test results

- task: PublishTestResults@2
  inputs:
    testResultsFormat: 'VSTest'
    testResultsFiles: '**/*.trx'
Enter fullscreen mode Exit fullscreen mode

Automated test results published from a pipeline (as shown in Section 4) surface in Azure DevOps' test reporting alongside manual Test Plans results, giving one combined view of both automated and manual test coverage and outcomes for a given build.


11. Self-Hosted Agents

Why self-host

pool:
  name: 'MySelfHostedPool'
Enter fullscreen mode Exit fullscreen mode

Microsoft-hosted agents (the default vmImage-based pool) are convenient and require no maintenance, but come with constraints: limited build minutes on free tiers, no persistent state between runs (every job starts from a completely fresh VM), and no access to resources behind a private network without additional configuration. Self-hosted agents — installed on your own VMs, on-prem servers, or containers — remove these constraints, at the cost of you now owning the agent's maintenance, patching, and scaling.

Setting up a self-hosted agent

./config.sh --url https://dev.azure.com/myorg --auth pat --token $PAT --pool MySelfHostedPool --agent myAgent1
./run.sh
Enter fullscreen mode Exit fullscreen mode

A self-hosted agent registers itself with a specific agent pool, and pipelines that specify that pool's name will be scheduled onto it — common reasons to self-host include needing network access to an internal, non-internet-facing resource (an on-prem database, an internal API), needing specialized hardware (GPU-equipped build machines), or wanting persistent caching between runs without re-downloading dependencies every time (similar to the caching motivations covered in this series' GitHub Actions guide, but achieved here through the agent's own persistent filesystem rather than an explicit cache action).


12. Azure DevOps vs. GitHub Actions/GitHub

Aspect Azure DevOps GitHub Actions + GitHub
Source control Azure Repos (Git or legacy TFVC) GitHub repositories
CI/CD Azure Pipelines (YAML or classic UI) GitHub Actions (YAML only)
Work tracking Azure Boards — deep Scrum/Kanban support, work item hierarchy GitHub Issues + Projects — lighter-weight, less formal process support
Package feeds Azure Artifacts (NuGet, npm, Maven, Python, universal) GitHub Packages (similar format support)
Manual test management Azure Test Plans — dedicated, structured tool No first-party equivalent; typically a third-party tool
Marketplace/ecosystem Smaller marketplace of tasks Much larger community Marketplace of Actions
Governance (extends templates) Strong — mandatory base templates enforceable org-wide Reusable workflows exist, but less of a hard "mandatory extension" mechanism
Typical organization fit Enterprises wanting integrated project management + CI/CD in one product, often with existing Microsoft/Azure DevOps investment Teams prioritizing open-source ecosystem size, GitHub-native workflows, broader community tooling

They aren't mutually exclusive

A common, entirely supported setup: source code on GitHub, with Azure Pipelines still handling CI/CD (Azure Pipelines can build directly from a GitHub repository via a service connection), especially in organizations mid-migration between the two ecosystems, or specifically wanting Azure Pipelines' deployment strategy features or self-hosted agent flexibility while keeping GitHub's broader community/ecosystem benefits for the code itself.

Practical guidance

  • Already invested in Azure DevOps, need deep Scrum/Kanban work tracking integrated with CI/CD, or need Azure Test Plans' manual test management? → Azure DevOps remains a strong, cohesive choice.
  • Prioritizing the largest possible community/Marketplace ecosystem, already living in GitHub for source control, or building open-source software? → GitHub Actions is generally the more natural fit.
  • Neither choice locks you out of the other — many organizations mix and match based on which specific service (Repos vs. Pipelines vs. Boards) best fits a given team's actual need.

Quick Reference Table

Concept Purpose
Azure Repos Git-based source control with branch policies
Azure Pipelines YAML-based CI/CD, stages/jobs/steps, deployment jobs
Variable groups Centrally-managed, shared pipeline configuration
Environments Approval gates and deployment history per target
Templates / extends Reusable and (optionally) organization-mandated pipeline structure
Azure Boards Work item hierarchy, sprints, Kanban boards
Azure Artifacts Private NuGet/npm/Maven/Python package feeds with upstream proxying
Azure Test Plans Structured manual/exploratory test case management
Self-hosted agents Custom build infrastructure for network access, hardware, or persistent caching needs

Conclusion

Azure DevOps' distinguishing strength is breadth under one roof — source control, CI/CD, structured work tracking, package feeds, and manual test management, all sharing a permission model and cross-linking naturally (a commit referencing a work item, a pipeline run surfacing both automated and manual test results together). For organizations that want that level of integrated project management alongside their build and release automation — particularly ones already invested in the Microsoft ecosystem — it remains a genuinely strong, mature choice, with YAML pipelines today offering the same version-controlled, reviewable pipeline-as-code approach that GitHub Actions popularized.

The right choice between Azure DevOps and a GitHub-centric toolchain usually comes down to which surrounding ecosystem and process model fits a team better, not a meaningful gap in core CI/CD capability — both platforms can build, test, and deploy a .NET application reliably, and plenty of real organizations use pieces of both rather than treating it as an exclusive either/or decision.


Found this useful? Feel free to star the repo, open an issue with corrections, or share the pipeline template that saved your team from a dozen copy-pasted YAML files.

Top comments (0)