What Is an Azure Pipeline? CI/CD Explained

Posted on

Web Design

Posted at

What Is an Azure Pipeline?

Azure Pipelines is the part of Azure DevOps that combines continuous integration, continuous testing, and continuous delivery to automatically build, test, and deploy code projects to any destination.It can build, test, and deploy applications written in languages including Node.js, Python, Java, PHP, Ruby, C#, C++, Go, and .NET, running on Linux, macOS, or Windows. A pipeline is defined mostly in YAML, checked into your repository alongside your code, and runs automatically whenever you push a commit, open a pull request, or trigger it manually. It can deploy to Azure services, other clouds, on-premises servers, or containers — Azure Pipelines is the automation engine, not a destination limited to Azure infrastructure.

What Problem Does Azure Pipelines Solve?

Before CI/CD tooling existed, shipping software usually looked like this: a developer writes code, manually builds it on their own machine, manually runs whatever tests they remember to run, manually packages the result, and manually uploads it to a server — often at the end of a long day, often skipping a step under deadline pressure.

That process breaks down as teams grow, because it depends entirely on individual discipline. Two developers might build the same project slightly differently. A test might get skipped "just this once." A deployment might happen from someone's laptop instead of a clean, repeatable environment — meaning nobody can be fully certain what was actually deployed.

Azure Pipelines automates that sequence:

Commit → Build → Test → Package → Deploy

The same steps run the same way every time, on a machine dedicated to the job rather than someone's laptop. This doesn't eliminate every deployment error — a bad pipeline can still automate a mistake just as reliably as it automates a success — but it removes the variability of manual process, gives every change a consistent build and test pass, and creates a traceable record of exactly what was built, tested, and deployed, and when.

What Is Azure Pipelines?

Azure Pipelines is one specific service inside the broader Azure DevOps suite, which also includes Azure Repos (source control), Azure Boards (work tracking), Azure Artifacts (package management), and Azure Test Plans (manual/exploratory testing). Azure Pipelines is the part responsible for automation: taking source code and turning it into a tested, deployable, and ultimately deployed application.

A simple mental model:

Developer pushes code → Pipeline starts → Application builds → Tests run → Artifact is created → Application deploys

Walking through each step:

  • Developer pushes code — a commit lands in a Git repository (Azure Repos, GitHub, or another supported source)

  • Pipeline starts — a trigger detects the new commit and kicks off the pipeline automatically

  • Application builds — the source code is compiled or assembled into a runnable form

  • Tests run — automated tests check that the change didn't break existing functionality

  • Artifact is created — the tested, built output is packaged into a reusable unit (a compiled binary, a container image, a deployable package)

  • Application deploys — that exact artifact is pushed to a target environment, whether that's a staging server, a production environment, or both in sequence

Azure Pipelines requires source code to be in a version control system — it doesn't operate on loose files sitting on a developer's machine; it needs a repository it can watch and check out from.

CI/CD Explained

CI/CD is one of those terms people use as a single phrase even though it actually describes two (or three) related but distinct practices.

What Is Continuous Integration (CI)?

Continuous Integration means developers merge their code changes into a shared branch frequently — often multiple times a day — and each merge automatically triggers a build and a test run. The goal is to catch integration problems and bugs early, while the change is small and fresh in the developer's mind, rather than weeks later when a dozen changes have piled on top of it and the bug could be anywhere.

What Is Continuous Delivery?

Continuous Delivery extends CI one step further: after a change passes its build and tests, it's automatically packaged into a release-ready artifact and prepared for deployment. The deployment itself, though, still requires a deliberate trigger — usually a manual approval — before it goes to production.

What Is Continuous Deployment?

Continuous Deployment goes further still: once a change passes all automated checks, it's deployed automatically, with no manual approval gate at all. Every change that passes the pipeline ends up in production.

These are genuinely different practices, not interchangeable synonyms:

Concept

Meaning

CI (Continuous Integration)

Build and test changes automatically, frequently

Continuous Delivery

Keep every passing change ready for deployment, with a manual gate before it actually ships

Continuous Deployment

Automatically deploy every validated change, with no manual gate

Most teams practicing "CI/CD" in production are actually doing continuous integration plus continuous delivery — build and test are fully automatic, but a human still approves the final push to production. Fully automatic continuous deployment to production is less common and usually reserved for teams with very mature automated testing.

Azure Pipelines Architecture

At a high level, a pipeline run moves through this chain:

Developer → Git Repository → Pipeline (Trigger) → Agent → Build/Test → Artifact → Environment → Deployment

Each component plays a specific role:

  • Repository — where your source code and pipeline YAML file live

  • Pipeline — the overall automated workflow definition

  • Trigger — the event that starts a pipeline run (a push, a pull request, a schedule, or a manual click)

  • Agent — the actual machine that executes the pipeline's work

  • Agent pool — a group of agents a pipeline can draw from

  • Stage — a major phase of the pipeline, such as Build, Test, or Deploy

  • Job — a set of steps that run together on a single agent

  • Task — a reusable, pre-built action (like "restore NuGet packages" or "publish artifact") used inside a step

  • Artifact — the packaged output of a build, passed along to later stages

  • Environment — a named deployment target (like Staging or Production) that tracks deployment history and can enforce approvals

  • Deployment — the act of delivering the artifact to an environment

How Azure Pipelines Works, Step by Step

  1. Developer commits code to a branch in the repository

  2. A trigger starts the pipeline — commonly a push to a watched branch

  3. An agent picks up the job from the assigned agent pool

  4. Dependencies are installed (npm packages, NuGet packages, pip requirements, etc.)

  5. The application is built — compiled, transpiled, or otherwise assembled

  6. Automated tests run against the build output

  7. A build artifact is created and published for later stages to consume

  8. A deployment stage starts, typically targeting a lower environment like staging first

  9. Approvals or checks may run before a sensitive environment (especially production) is touched

  10. The application is deployed to the target environment

  11. Deployment is monitored or validated, often with a post-deployment smoke test or health check

Not every pipeline uses all eleven steps — a simple CI-only pipeline might stop after step 7, publishing an artifact without deploying it anywhere. A full CI/CD pipeline runs the complete chain.

Azure Pipelines Components

Understanding the hierarchy makes everything else in this guide easier to follow:

Pipeline → Stage → Job → Step → Task

  • Pipeline — the complete automation workflow, defined in one YAML file (or a set of linked YAML files)

  • Stage — a major phase, such as Build, Test, or Deploy; a stage can hold up to 256 jobs</cite>

  • Job — a collection of steps that run together, on one agent

  • Step — an individual operation within a job — steps run sequentially on the same agent, and can be a script, a bash or PowerShell command, a checkout, a download, or a task

  • Task — a pre-built, reusable action provided by Azure DevOps or a marketplace extension (for example, a task that publishes test results, or one that deploys to Azure App Service)

  • Agent — the machine that actually executes a job's steps

  • Artifact — the packaged output of a build stage, consumed by later stages

  • Environment — a named deployment target that Azure DevOps tracks deployment history against, and where approvals and checks are configured

Azure Pipelines Agents

An agent is the compute environment where your pipeline's steps actually execute. Azure Pipelines runs on agents — compute environments that execute your pipeline steps.

Microsoft-hosted agents — managed virtual machines with pre-installed tooling for Windows, Linux, and macOS. They're zero-maintenance, but have fixed specifications and time limits on individual jobs.

Self-hosted agents — your own machines, VMs, or containers. They give you full control over the environment but require you to manage updates, scaling, and security yourself.

Feature

Microsoft-hosted

Self-hosted

Infrastructure management

Microsoft-managed

Customer-managed

Custom software/tooling

Limited to what's pre-installed (or installable per-run)

High flexibility — install anything

Maintenance burden

Low

Higher (patching, scaling, security)

Job time limits

Fixed limits apply

No inherent Microsoft-imposed limit

Best for

Standard builds, most common languages/frameworks

Specialized hardware, licensed software, private network access

Teams typically start with Microsoft-hosted agents and move to self-hosted agents when they need something the hosted images don't provide — access to an internal network, a specific licensed compiler, GPU hardware, or tighter control over exactly what's installed on the build machine.

YAML vs. Classic Pipelines

Azure DevOps has historically supported two ways to define a pipeline.

YAML pipelines define the entire pipeline as code, in a file (conventionally azure-pipelines.yml) checked into the same repository as your application. This means the pipeline definition is version-controlled, reviewable in pull requests, and reusable across branches, just like application code.

Classic pipelines use a visual, point-and-click editor in the Azure DevOps web interface instead of a YAML file. Classic pipelines remain supported but are in maintenance mode — Microsoft has been investing new features almost exclusively in the YAML model.

For any new project, YAML is the recommended and far more common approach, since it's reviewable, diffable, and portable in a way a visual editor's configuration isn't. Classic pipelines still exist for teams with legacy setups or for scenarios where a purely visual designer is preferred, but they aren't where new capability is being added.

What Is an Azure YAML Pipeline?

An Azure YAML pipeline is a pipeline whose entire definition — triggers, stages, jobs, steps, and tasks — lives in a .yml file, most commonly named azure-pipelines.yml, at the root of your repository.

A minimal example:

trigger:
- main

pool:
  vmImage: ubuntu-latest

steps:
- script: echo "Build started"
  displayName: "Build"
trigger:
- main

pool:
  vmImage: ubuntu-latest

steps:
- script: echo "Build started"
  displayName: "Build"
trigger:
- main

pool:
  vmImage: ubuntu-latest

steps:
- script: echo "Build started"
  displayName: "Build"

What each part does:

  • trigger: - main tells Azure Pipelines to run this pipeline automatically whenever a commit lands on the main branch

  • pool: vmImage: ubuntu-latest tells the pipeline to run on a Microsoft-hosted Ubuntu agent

  • steps: begins the list of steps the job will execute

  • - script: echo "Build started" runs a single shell command

  • displayName: "Build" gives that step a readable name in the pipeline's run logs

This is intentionally the simplest possible pipeline — a real build pipeline replaces the placeholder script with actual dependency-install, compile, and test commands.

A Complete CI Pipeline Example (Node.js)

A CI-only pipeline — build and test, no deployment — for a Node.js project:

trigger:
- main

pool:
  vmImage: ubuntu-latest

steps:
- task: NodeTool@0
  inputs:
    versionSpec: '20.x'
  displayName: 'Install Node.js'

- script: npm install
  displayName: 'Install dependencies'

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

- script: npm test
  displayName: 'Run tests'

- task: PublishBuildArtifacts@1
  inputs:
    PathtoPublish: '$(System.DefaultWorkingDirectory)/dist'
    ArtifactName: 'drop'
  displayName: 'Publish build artifact'
trigger:
- main

pool:
  vmImage: ubuntu-latest

steps:
- task: NodeTool@0
  inputs:
    versionSpec: '20.x'
  displayName: 'Install Node.js'

- script: npm install
  displayName: 'Install dependencies'

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

- script: npm test
  displayName: 'Run tests'

- task: PublishBuildArtifacts@1
  inputs:
    PathtoPublish: '$(System.DefaultWorkingDirectory)/dist'
    ArtifactName: 'drop'
  displayName: 'Publish build artifact'
trigger:
- main

pool:
  vmImage: ubuntu-latest

steps:
- task: NodeTool@0
  inputs:
    versionSpec: '20.x'
  displayName: 'Install Node.js'

- script: npm install
  displayName: 'Install dependencies'

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

- script: npm test
  displayName: 'Run tests'

- task: PublishBuildArtifacts@1
  inputs:
    PathtoPublish: '$(System.DefaultWorkingDirectory)/dist'
    ArtifactName: 'drop'
  displayName: 'Publish build artifact'

The flow: Checkout (automatic) → Install Node.js → Install dependencies → Build → Test → Publish artifact. Each step depends on the previous one succeeding; if npm test fails, the pipeline stops there and the artifact is never published — which is the intended behavior, since you don't want to package and later deploy code that failed its tests.

A Complete CI/CD Pipeline Example

Extending the idea to a full build-through-production pipeline, using placeholders for anything environment-specific:

trigger:
- main

pool:
  vmImage: ubuntu-latest

stages:
- stage: Build
  jobs:
  - job: BuildJob
    steps:
    - script: npm install
      displayName: 'Install dependencies'
    - script: npm run build
      displayName: 'Build application'
    - task: PublishBuildArtifacts@1
      inputs:
        PathtoPublish: '$(System.DefaultWorkingDirectory)/dist'
        ArtifactName: 'drop'
      displayName: 'Publish build artifact'

- stage: Test
  dependsOn: Build
  jobs:
  - job: TestJob
    steps:
    - script: npm test
      displayName: 'Run automated tests'

- stage: DeployToStaging
  dependsOn: Test
  jobs:
  - deployment: DeployStaging
    displayName: 'Deploy to Staging'
    environment: 'staging'
    pool:
      vmImage: ubuntu-latest
    strategy:
      runOnce:
        deploy:
          steps:
          - script: echo "Deploying build $(Build.BuildId) to staging"
            displayName: 'Deploy to staging environment'

- stage: DeployToProduction
  dependsOn: DeployToStaging
  condition: succeeded()
  jobs:
  - deployment: DeployProduction
    displayName: 'Deploy to Production'
    environment: 'production'
    pool:
      vmImage: ubuntu-latest
    strategy:
      runOnce:
        deploy:
          steps:
          - script: echo "Deploying build $(Build.BuildId) to production"
            displayName: 'Deploy to production environment'
trigger:
- main

pool:
  vmImage: ubuntu-latest

stages:
- stage: Build
  jobs:
  - job: BuildJob
    steps:
    - script: npm install
      displayName: 'Install dependencies'
    - script: npm run build
      displayName: 'Build application'
    - task: PublishBuildArtifacts@1
      inputs:
        PathtoPublish: '$(System.DefaultWorkingDirectory)/dist'
        ArtifactName: 'drop'
      displayName: 'Publish build artifact'

- stage: Test
  dependsOn: Build
  jobs:
  - job: TestJob
    steps:
    - script: npm test
      displayName: 'Run automated tests'

- stage: DeployToStaging
  dependsOn: Test
  jobs:
  - deployment: DeployStaging
    displayName: 'Deploy to Staging'
    environment: 'staging'
    pool:
      vmImage: ubuntu-latest
    strategy:
      runOnce:
        deploy:
          steps:
          - script: echo "Deploying build $(Build.BuildId) to staging"
            displayName: 'Deploy to staging environment'

- stage: DeployToProduction
  dependsOn: DeployToStaging
  condition: succeeded()
  jobs:
  - deployment: DeployProduction
    displayName: 'Deploy to Production'
    environment: 'production'
    pool:
      vmImage: ubuntu-latest
    strategy:
      runOnce:
        deploy:
          steps:
          - script: echo "Deploying build $(Build.BuildId) to production"
            displayName: 'Deploy to production environment'
trigger:
- main

pool:
  vmImage: ubuntu-latest

stages:
- stage: Build
  jobs:
  - job: BuildJob
    steps:
    - script: npm install
      displayName: 'Install dependencies'
    - script: npm run build
      displayName: 'Build application'
    - task: PublishBuildArtifacts@1
      inputs:
        PathtoPublish: '$(System.DefaultWorkingDirectory)/dist'
        ArtifactName: 'drop'
      displayName: 'Publish build artifact'

- stage: Test
  dependsOn: Build
  jobs:
  - job: TestJob
    steps:
    - script: npm test
      displayName: 'Run automated tests'

- stage: DeployToStaging
  dependsOn: Test
  jobs:
  - deployment: DeployStaging
    displayName: 'Deploy to Staging'
    environment: 'staging'
    pool:
      vmImage: ubuntu-latest
    strategy:
      runOnce:
        deploy:
          steps:
          - script: echo "Deploying build $(Build.BuildId) to staging"
            displayName: 'Deploy to staging environment'

- stage: DeployToProduction
  dependsOn: DeployToStaging
  condition: succeeded()
  jobs:
  - deployment: DeployProduction
    displayName: 'Deploy to Production'
    environment: 'production'
    pool:
      vmImage: ubuntu-latest
    strategy:
      runOnce:
        deploy:
          steps:
          - script: echo "Deploying build $(Build.BuildId) to production"
            displayName: 'Deploy to production environment'

Notes on this example:

  • The environment: 'production' reference is where a manual approval gets attached — Approvals aren't written directly into the YAML; a resource owner attaches a manual approval check to the production environment itself through the Azure DevOps web interface, and any stage that deploys to that environment is blocked until the check passes.This means the same pipeline can deploy to a dev environment with zero friction and to production behind a required sign-off, without any extra branching logic in the YAML.

  • deployment: (rather than a plain job:) is a special job type — deployment jobs are designed for deploying applications, and provide deployment history, rollback support, and integration with Azure DevOps environments.

  • strategy: runOnce: is the simplest deployment strategy, deploying once with no gradual rollout; more advanced strategies (rolling, canary) are covered later in this guide.

  • Real projects would replace the echo placeholders with actual deployment tasks (for example, AzureWebApp@1 to deploy to Azure App Service), and would reference a service connection for authentication rather than embedding any credentials directly.

Azure Pipeline Triggers

Triggers determine what causes a pipeline to run:

  • CI triggers — run automatically on a push to specified branches (e.g., trigger: - main)

  • Pull request triggers — run a validation pipeline automatically when a PR is opened or updated, before code is merged

  • Scheduled triggers — run on a defined schedule (e.g., nightly regression tests), independent of any code push

  • Pipeline completion triggers — start one pipeline automatically when another pipeline completes, useful for chaining build and deploy pipelines that are defined separately

  • Manual runs — a pipeline can always be started manually from the Azure DevOps UI or CLI, regardless of other trigger configuration

Common patterns:

Push to main → CI pipeline runs, validating the change is safe to build on top of.

Pull request opened → Validation pipeline runs, giving reviewers a pass/fail signal before they approve the merge.

Nightly schedule → Full regression suite runs, catching issues that a fast per-commit test suite intentionally skips for speed.

Branch-Based CI/CD

Most teams organize pipeline behavior around their branching model — commonly some combination of a main branch, a develop branch, short-lived feature branches, and sometimes dedicated release branches.

Typical controls layered on top of branches:

  • Pull requests as the required path for merging into protected branches

  • Branch policies that require a passing build before a PR can merge

  • Required reviewers as part of code review

  • Deployment controls that only trigger production deployment from specific branches (commonly main or a release branch), not from arbitrary feature branches

There's no single branching strategy that's universally correct — trunk-based development, Git Flow, and simpler main-plus-feature-branches setups are all used successfully by different teams, and the right choice depends on release cadence, team size, and how much parallel work is happening at once.

Build Pipelines

A build stage typically includes:

  1. Source checkout — pulling the current commit's code onto the agent

  2. Dependency installation — restoring packages the project needs (npm, NuGet, pip, Maven, etc.)

  3. Compilation (for compiled languages) or transpilation (for languages like TypeScript)

  4. Linting — automated style and error checking

  5. Unit testing — fast, isolated tests of individual components

  6. Packaging — assembling the build output into a deployable unit

  7. Artifact creation — publishing that packaged output for later stages

Representative commands by ecosystem:

  • .NET: dotnet restore, dotnet build, dotnet test

  • Node.js: npm install, npm run build, npm test

  • Python: pip install -r requirements.txt, then a test runner like pytest

  • Java (Maven): mvn install, which handles dependency resolution, build, and test in one command by default

Automated Testing in Azure Pipelines

Tests commonly run at multiple levels within a pipeline:

  • Unit tests — fast, isolated checks of individual functions or components, typically run on every commit

  • Integration tests — checks that multiple components work correctly together, often run less frequently than unit tests due to longer run times

  • End-to-end tests — checks that simulate real user flows through the whole application

  • Smoke tests — a small, fast set of checks run immediately after deployment to confirm the application is at least minimally functional

  • Security checks — automated scanning for known vulnerabilities in dependencies or code

  • Code quality checks — static analysis for style, complexity, or maintainability issues

Tests should run before a deployment stage, not after. A pipeline is typically configured so that if any test step fails, the pipeline stops there — the deployment stages never execute, and nothing untested reaches an environment.

Artifacts in Azure Pipelines

A pipeline artifact is the packaged output of a build — a compiled binary, a zipped web app, a container image reference, or similar — published so that later stages (or later pipelines) can consume it without rebuilding.

Why this matters: you want to deploy the exact same artifact you tested, not rebuild the code separately for each environment. If staging and production each triggered their own independent build from source, you'd introduce the possibility that a dependency update, a compiler version difference, or an environment difference between the two builds produces subtly different code — which defeats the purpose of testing in the first place. Publishing one artifact from a single build stage, then promoting that same artifact through staging and production, guarantees that what you tested is exactly what you shipped.

Azure Pipeline Stages

Stages represent the major phases of a pipeline — typically Build, Test, Staging, and Production, though the exact set varies by project.

Stages support:

  • Dependencies (dependsOn) — controlling which stages must complete before another starts

  • Conditions (condition) — controlling whether a stage runs at all, based on the outcome of previous stages or other logic (e.g., condition: succeeded())

  • Approvals and checks — attached at the environment level, gating deployment stages until a human (or an automated check) signs off

Breaking a pipeline into stages rather than one long flat sequence of steps makes complex delivery workflows far easier to reason about, review, and partially re-run — for example, re-running just a failed deployment stage without rebuilding from scratch.

Azure Pipeline Jobs and Steps

A practical example of the hierarchy in a deploy stage:

Deploy Stage → Production Deployment Job   → Download artifact   → Log in (via service connection)   → Deploy   → Run smoke test

Jobs can also run in parallel where it makes sense — for example, running a Windows build job and a Linux build job for the same commit simultaneously, rather than one after another, to cut down total pipeline time.

Variables in Azure Pipelines

Variables let a pipeline avoid hardcoding values that change between environments or runs:

  • Pipeline variables — defined directly in the YAML or pipeline settings

  • Variable groups — a named set of variables (often shared across multiple pipelines) managed centrally in the Azure DevOps library

  • Runtime variables — set or overridden at the moment a pipeline is triggered

  • Template variables — passed into a reusable YAML template

  • Environment variables — standard OS-level environment variables exposed to running scripts

Variables are especially useful for values like a target resource name, a connection string reference, or a build configuration flag that needs to differ between staging and production without duplicating the entire pipeline definition.

Sensitive values should never be stored as plain pipeline variables in YAML — that's what secret variables and Key Vault integration (next section) are for.

Secrets and Secure Variables

This deserves its own section because it's one of the most common sources of real incidents in CI/CD systems.

Never commit to source control:

  • Passwords

  • API keys

  • Connection strings

  • Cloud credentials

  • Private access tokens

Instead, Azure Pipelines supports:

  • Secret variables — variables marked as secret in the pipeline UI or variable group, which are masked in logs and not exposed in plain text in the YAML file

  • Azure Key Vault integration — secrets can be pulled directly from Key Vault at pipeline run time rather than stored in Azure DevOps at all

  • Service connections — scoped, managed credentials for authenticating to external resources (see below), rather than embedding raw credentials in a script step

Practical recommendations: store secrets in Key Vault or as secret variables (never as plain variables), scope service connections to the minimum permissions actually required, and avoid echo-ing secret values in scripts, since even masked-in-UI values can sometimes leak into logs if handled carelessly by a script step.

Service Connections

A service connection is how Azure Pipelines authenticates to an external resource — most commonly an Azure subscription, but also container registries, other cloud providers, or third-party services.

Rather than a pipeline step containing raw credentials, it references a named service connection that Azure DevOps manages centrally. This has two practical benefits: credentials aren't scattered across YAML files, and permissions can be managed and revoked in one place if a connection needs to be rotated or removed.

Least privilege matters here — a service connection used only to deploy to a specific App Service shouldn't have subscription-wide Owner permissions. Scoping each connection as narrowly as the task actually requires limits the blast radius if a pipeline or its credentials are ever compromised.

Azure Key Vault Integration

Azure Key Vault can store secrets independently of Azure DevOps, with pipelines retrieving them at run time rather than storing a copy inside the pipeline's own variable store.

This separation is useful because:

  • Secret rotation happens in Key Vault, not by editing multiple pipelines

  • Access to secrets can be controlled through Azure's own identity and access management, layered on top of Azure DevOps permissions

  • Secrets retrieved from Key Vault are still subject to Azure Pipelines' log-masking behavior, but the underlying source of truth lives outside the pipeline definition itself

Avoid printing retrieved secret values to the console in a script step — even with masking, treating secrets as "never touch a raw echo/print statement" is the safer default.

Environments in Azure Pipelines

An environment is a named deployment target — Dev, Staging, Production — that can hold resources like Kubernetes namespaces or virtual machines, and that resource owners attach approvals and checks to.

Environments give you:

  • Deployment tracking — a history of every deployment made to that environment, tied to the pipeline run and commit that produced it

  • Approvals — manual sign-off requirements before a stage can deploy to that environment

  • Checks — additional automated gates (for example, verifying a change ticket exists, or enforcing a business-hours deployment window)

  • Environment-level security — controlling who is even allowed to deploy to a given environment, separate from who can edit the pipeline YAML

This is a deliberate design choice in Azure Pipelines: a user who can modify the pipeline YAML file cannot also modify the checks enforced before a stage starts — those are managed separately by the resource (environment) owner through the Azure DevOps web interface. That separation prevents someone from simply editing the YAML to bypass a required production approval.

Approvals and Checks

Approvals exist to prevent an automated pipeline from making a decision that should genuinely involve a human — most commonly, "should this go to production right now."

  • Manual approvals — a designated reviewer must explicitly approve before a stage proceeds

  • Branch controls — restricting which branches are even eligible to trigger a deployment to a given environment

  • Business-hour restrictions — some teams gate production deployments to specific windows, avoiding risky changes right before a weekend or holiday

  • Quality and security checks — automated gates that must pass (e.g., no critical vulnerabilities found) before deployment proceeds

The trade-off worth being explicit about: approvals add safety, but excessive approval layers — multiple sequential sign-offs for even trivial changes — slow delivery and can push teams toward batching changes into larger, riskier releases just to avoid going through the approval process repeatedly. The goal is proportional control: heavier gates on production and on higher-risk changes, lighter or no gates on lower-risk environments like dev.

Deployment Strategies

Strategy

Main Idea

Best For

Rolling

Replace running instances gradually, a few at a time

Maintaining continuous availability during rollout

Blue-Green

Deploy the new version alongside the old, then switch traffic over

Safer releases with a fast rollback path (switch traffic back)

Canary

Route a small percentage of traffic to the new version before a full rollout

Reducing blast radius while validating a change with real traffic

Recreate

Stop the old version, then start the new one

Simpler deployments where brief downtime is acceptable

The exact mechanics of each strategy depend heavily on the target platform — a rolling deployment looks different on Azure Kubernetes Service than it does on a set of Azure VMs, and Azure Pipelines' built-in runOnce, rolling, and canary deployment strategy keywords in a deployment: job provide the orchestration, but the underlying infrastructure has to actually support the strategy you're choosing.

Azure Services You Can Deploy To

Common deployment targets include:

  • Azure App Service — for web applications and APIs

  • Azure Functions — for serverless workloads

  • Azure Kubernetes Service (AKS) — for containerized applications running on Kubernetes

  • Azure Container Apps — for containerized workloads without managing full Kubernetes

  • Azure VMs — for applications requiring full control over the underlying machine

  • Azure Static Web Apps — for static front-end sites and Jamstack apps

  • Container registries (like Azure Container Registry) — as a step before deploying container-based workloads

Azure Pipelines is not limited to Azure targets — it can also deploy to AWS, Google Cloud, on-premises servers, or any environment reachable through a suitable task or script, provided the right credentials and connection are configured. This is worth emphasizing because it's a common misconception: using Azure Pipelines does not require deploying to Azure.

Azure Pipelines for Containers

A common containerized workflow:

Code → Build Docker Image → Test → Push Image → Deploy Container

Key elements:

  • Docker — used to build the application into a container image as part of the pipeline

  • Azure Container Registry (or another registry) — where built images are pushed and versioned

  • Image tagging — tagging each image with something traceable, like the build ID or commit hash, rather than reusing a mutable tag like latest

  • Image scanning — checking the built image for known vulnerabilities before it's deployed

  • Deployment — pulling the specific tagged image into the target environment

Using immutable, uniquely tagged images (rather than overwriting a shared tag) matters for the same reason artifact reuse matters in non-container pipelines: it guarantees that what was tested is exactly what gets deployed, and makes rollback straightforward — you redeploy the previous known-good tag.

Azure Pipelines for Kubernetes

A typical flow:

Source → Build → Container Image → Registry → Kubernetes Deployment

Relevant pieces:

  • AKS (Azure Kubernetes Service) — a common managed Kubernetes target, though Azure Pipelines can deploy to any Kubernetes cluster it has network access and credentials for

  • Kubernetes manifests — YAML files describing the desired state of your deployment, separate from the pipeline's own YAML

  • Helm — a package manager for Kubernetes, often used to templatize manifests across environments

  • Secrets — handled through Kubernetes secrets or an external secret store, not embedded in manifests

  • Deployment strategies — rolling updates are Kubernetes' native default behavior; canary and blue-green typically require additional tooling or manual traffic-management configuration on top of base Kubernetes

For teams new to this combination, it's worth keeping the pipeline's job simple — build, test, push the image, apply the manifest — and letting Kubernetes' own deployment mechanics handle the rollout, rather than trying to reimplement Kubernetes' job inside the pipeline.

Multi-Stage YAML Pipelines

A multi-stage pipeline structures the whole delivery process — not just the build — as one connected YAML definition:

Pipeline
 ├── Build
 ├── Test
 ├── Deploy-Staging
 └── Deploy-Production
Pipeline
 ├── Build
 ├── Test
 ├── Deploy-Staging
 └── Deploy-Production
Pipeline
 ├── Build
 ├── Test
 ├── Deploy-Staging
 └── Deploy-Production

The pieces that tie these stages together:

  • Dependencies (dependsOn) — controlling execution order

  • Conditions (condition) — controlling whether a stage runs based on the outcome of earlier stages

  • Variables — shared or stage-specific configuration values

  • Approvals — attached to the environments referenced by deployment stages

  • Artifacts — published once in Build, then consumed by every later stage that needs them

This structure is what makes the earlier full CI/CD YAML example work as a single, self-contained, reviewable pipeline definition rather than several disconnected pipelines.

Templates and Reusable Pipelines

As an organization's number of pipelines grows, copy-pasting the same YAML into every repository becomes a maintenance problem — a security fix or a new required step has to be manually applied everywhere.

Azure Pipelines addresses this with templates:

  • Stage templates — reuse an entire stage definition (e.g., a standardized "Deploy" stage) across pipelines

  • Job templates — reuse a job definition (e.g., a standardized "Run tests and publish results" job)

  • Step templates — reuse a sequence of steps (e.g., a standard "restore, build, and scan" sequence)

  • Parameters — templates accept parameters, so the same reusable template can adjust behavior (language version, target OS, container image) per calling pipeline

A conceptual example: a central "deploy-to-app-service" step template could accept an app name and environment as parameters, and every application team's pipeline calls that one template rather than reimplementing App Service deployment logic from scratch. When the deployment logic needs a security update, it changes in exactly one place.

Azure Pipelines vs. GitHub Actions

Feature

Azure Pipelines

GitHub Actions

CI/CD

Yes

Yes

Configuration

YAML

YAML

GitHub integration

Works with GitHub as a source, alongside Git, SVN, and TFVC

Native — built directly into GitHub

Azure integration

Strong, native service connections

Strong, via published actions and service connections

Ecosystem

Azure DevOps ecosystem (Boards, Repos, Artifacts, Test Plans)

GitHub ecosystem, plus a large marketplace of community-created actions

Enterprise controls

Strong (environments, checks, approvals, RBAC)

Strong (environments, required reviewers, branch protection)

Best fit

Teams already using Git, SVN, or TFVC repositories and wanting a unified DevOps toolchain

Teams whose code already lives in GitHub and want CI/CD without leaving that ecosystem

GitHub Actions is generally considered easier to learn, especially for people already familiar with GitHub workflows, and it works exclusively with GitHub repositories.Azure Pipelines' advantage is depth of integration with the rest of Azure DevOps (Boards for work tracking, Artifacts for packages, Test Plans for manual testing) and support for source control systems beyond Git. Neither is a universal "better" choice — teams standardized on GitHub for source control lean toward GitHub Actions; teams using Azure DevOps as their full project-management-plus-delivery platform, or needing non-Git source control support, lean toward Azure Pipelines.

Azure Pipelines vs. Jenkins

Jenkins is a self-hosted, open-source automation server with a large plugin ecosystem, in contrast to Azure Pipelines' hosted-service model (with a self-hosted agent option).

Consideration

Azure Pipelines

Jenkins

Hosting model

Hosted service (with self-hosted agent option)

Fully self-managed infrastructure

Setup and maintenance

Lower — Microsoft manages the control plane

Higher — you run and maintain the Jenkins server itself

Customization

High, via tasks/extensions

Very high, via a large plugin ecosystem

Azure integration

Native

Available via plugins, less integrated by default

Operational overhead

Lower

Higher (patching, scaling, plugin management)

Jenkins still makes sense for teams that want maximum control over every aspect of their build infrastructure, have existing deep Jenkins expertise, or need a specific plugin from Jenkins' very large ecosystem that doesn't have an equivalent in Azure Pipelines. Teams that would rather not operate their own CI server at all generally find Azure Pipelines' hosted model less operational overhead.

Azure Pipelines vs. GitLab CI/CD

GitLab CI/CD is tightly integrated into the GitLab platform itself, similar in spirit to how GitHub Actions is tied to GitHub.

Key points of comparison:

  • Repository integration — GitLab CI/CD is built directly into GitLab repositories; Azure Pipelines can connect to Azure Repos, GitHub, and other sources, giving it more source-control flexibility

  • Configuration — both use YAML-based pipeline definitions

  • Platform integration — GitLab CI/CD is part of GitLab's own all-in-one DevOps platform (issues, repos, CI/CD, registry); Azure Pipelines is part of the equivalent Azure DevOps suite

  • Cloud ecosystem — Azure Pipelines has a natural advantage when deploying specifically into Azure, given native service connections and tasks; GitLab CI/CD is cloud-agnostic by design

As with the other comparisons, the more relevant question for most teams isn't "which CI/CD engine is objectively better" but "which platform is your team's source control and project management already built around."

Real-World Example: Deploying a Web Application

A typical team workflow, with Azure Pipelines' role marked at each step:

  1. Developer creates a feature branch and writes code

  2. A pull request is opened against main

  3. Azure Pipelines runs a CI/validation pipeline triggered by the PR

  4. Automated tests execute as part of that pipeline

  5. A reviewer reviews the code and the pipeline's pass/fail result

  6. The code merges into main

  7. A new pipeline run builds the merged code and publishes an artifact

  8. The pipeline deploys that artifact to a staging environment

  9. Smoke tests run automatically against staging

  10. A production approval is required before the next stage proceeds

  11. Once approved, the pipeline deploys the same artifact to production

  12. Monitoring and health checks validate that the deployment is actually healthy, not just "completed without error"

Azure Pipelines is the automation running steps 3, 4, 7, 8, 9, 11, and enforcing the approval gate in step 10 — the human decisions (code review, approval) remain human; the mechanical, repeatable work is automated.

Real-World Example: Enterprise Application

A larger organization typically layers more governance on top of the same core flow:

  • Multiple environments — dev, QA, staging, and production, each with its own approval requirements

  • Security checks built into the pipeline — dependency scanning, static analysis, and container scanning run automatically before code reaches staging

  • Approvals — potentially multi-person sign-off for production changes, sometimes tied to a formal change-management process

  • Infrastructure-as-code — infrastructure changes (not just application code) go through their own validate/plan/apply pipeline

  • Automated testing at multiple levels, including integration and end-to-end suites that run less frequently than fast unit tests

Enterprise pipelines typically need more than the individual pipeline mechanics — they need governance (who can approve what), access controls (who can even trigger a production deployment), auditability (a clear record of every change and who approved it), and reusable templates (so every team's pipeline enforces the same organizational standards without each team reinventing them).

Azure Pipelines Best Practices

  • Store pipelines as code (YAML), version-controlled alongside your application

  • Keep pipelines modular — separate build, test, and deploy concerns into distinct stages

  • Use reusable templates instead of duplicating YAML across repositories

  • Store secrets in secret variables or Key Vault, never in plain YAML

  • Scope service connections and permissions to least privilege

  • Automate testing at every relevant level (unit, integration, smoke)

  • Keep builds reproducible — pin dependency versions where it matters

  • Version and reuse artifacts rather than rebuilding per environment

  • Use genuinely separate environments for staging and production, not a single shared target

  • Add approvals where the risk of a bad deployment justifies the friction — not everywhere by default

  • Monitor deployments after they complete, not just whether the pipeline step itself succeeded

  • Keep logs useful and free of leaked secrets

  • Fail fast — put cheap, fast checks (like linting) earlier in the pipeline than slow ones

  • Avoid unnecessary pipeline complexity; a pipeline that's hard to read is hard to trust

  • Document non-obvious pipeline decisions (why a particular approval exists, why a step is ordered where it is) so the next engineer isn't guessing

Common Azure Pipeline Mistakes

  • Hardcoding secrets in YAML — fix by moving them to secret variables or Key Vault

  • Building directly on a production server — fix by using dedicated build agents, never production infrastructure, for build work

  • Skipping automated tests to save time — fix by treating test failures as pipeline failures, not warnings

  • Using overly broad permissions on service connections — fix by scoping each connection to only what it actually needs

  • Duplicating YAML across many pipelines — fix by extracting shared logic into templates

  • Building giant, monolithic pipelines — fix by splitting into clear stages with defined dependencies

  • Not versioning artifacts — fix by tagging builds with a traceable identifier (build ID, commit hash)

  • Ignoring failed builds — fix by treating a red pipeline as a stop-the-line signal, not background noise

  • Poor variable management (scattered, inconsistent naming) — fix with variable groups and consistent naming conventions

  • No rollback strategy — fix by keeping previous artifacts/images available and documenting the rollback procedure before you need it

  • No staging environment — fix by adding at least one pre-production environment that mirrors production closely enough to catch real issues

  • Overcomplicated branching strategies — fix by matching the branching model to actual team size and release cadence, not aspirational complexity

  • Unclear pipeline ownership — fix by assigning a clear owner for each pipeline and its associated environments

Troubleshooting Azure Pipelines

Pipeline does not trigger Check the trigger configuration in the YAML, confirm you're pushing to a branch the trigger actually watches, verify the YAML file path configured for the pipeline matches its actual location, and check repository/webhook settings if the connection itself might be broken.

Build fails Check that dependencies are correctly specified and installable, confirm the runtime/language version matches what the project expects, check whether the agent image has the tools you assumed it would, and read the actual build logs rather than guessing — the failure point is almost always visible there.

Deployment fails Check the service connection's validity and permissions, confirm the account/identity has access to the target environment, verify the target environment's current state matches what the pipeline expects, and check for configuration drift between what staging tested and what production expects.

Secrets are unavailable to a step Check the variable's scope (pipeline-level vs. stage-level vs. job-level), confirm Key Vault access policies actually grant the pipeline's identity permission to read the secret, and check that the relevant service connection has the access it needs.

Tests fail Check the actual test logs first, look for environment differences between where tests pass locally and where they run in the pipeline, and check for dependency version mismatches between the developer's machine and the agent.

Azure Pipeline Performance Optimization

  • Dependency caching — cache package manager directories (npm, NuGet, pip) between runs to avoid re-downloading the same dependencies every time

  • Parallel jobs — run independent jobs (like multi-OS builds or unrelated test suites) at the same time instead of sequentially

  • Reusable artifacts — build once, deploy the same artifact everywhere, instead of rebuilding per stage

  • Avoiding unnecessary builds — use path filters so a change to documentation doesn't trigger a full application build

  • Incremental workflows — where the toolchain supports it, avoid full rebuilds for small changes

  • Efficient test execution — run fast unit tests on every commit, and reserve slow end-to-end suites for less frequent triggers (like a nightly schedule or pre-merge gate)

  • Appropriate agent selection — don't default to the largest/most expensive agent type for jobs that don't need it

None of these should come at the cost of reliability — for example, caching that silently serves a stale dependency, or skipping a test suite to save time, trades a real risk for a speed gain that usually isn't worth it.

Cost Considerations

Azure Pipelines cost depends on several factors rather than a single flat number: how many parallel jobs you run concurrently, whether you're using Microsoft-hosted or self-hosted agents (self-hosted shifts cost to your own infrastructure instead of Microsoft's), how frequently pipelines run, artifact storage consumed, and any other Azure resources your pipeline provisions or interacts with along the way. Because pricing details and included free-tier allowances change over time, consult Microsoft's official Azure DevOps pricing page for current numbers rather than relying on a figure that may already be outdated.

How to Create Your First Azure Pipeline

  1. Create (or open) an Azure DevOps organization and project

  2. Connect or create a repository containing your application code

  3. Create a new pipeline from the Pipelines section of the project

  4. Choose YAML as the pipeline configuration format

  5. Select the repository the pipeline should watch and check out

  6. Create the YAML file — Azure DevOps can generate a starter template based on your project type, or you can write one from scratch

  7. Run the pipeline for the first time

  8. Review the logs for each step to confirm it did what you expected

  9. Add automated tests to the pipeline once the basic build works

  10. Add a deployment stage once build and test are working reliably

Beginner Project: Build and Test on Every Push to Main

A good first project: automatically build and test a small web application whenever code is pushed to main.

What that requires:

  • Repository — the application's source code in Azure Repos or GitHub

  • Triggertrigger: - main in the YAML

  • Agent — a Microsoft-hosted agent (pool: vmImage: ubuntu-latest is a reasonable default)

  • Build — install dependencies and produce a build output

  • Test — run the project's automated test suite

  • Artifact — publish the build output, even before you have anywhere to deploy it yet

Once that's working reliably, extending it into full CI/CD is a matter of adding a deployment stage that consumes the artifact you're already publishing — you don't need to redesign the pipeline, just extend it.

Advanced Azure Pipeline Architecture

A more complete enterprise-style flow:

Developer
   
Pull Request
   
CI Validation
   
Build
   
Security Scan
   
Test
   
Artifact
   
Staging
   
Integration Tests
   
Approval
   
Production
   
Monitoring
Developer
   
Pull Request
   
CI Validation
   
Build
   
Security Scan
   
Test
   
Artifact
   
Staging
   
Integration Tests
   
Approval
   
Production
   
Monitoring
Developer
   
Pull Request
   
CI Validation
   
Build
   
Security Scan
   
Test
   
Artifact
   
Staging
   
Integration Tests
   
Approval
   
Production
   
Monitoring

Each added layer exists to catch a different class of problem before it reaches users: pull-request validation catches obvious issues before merge, the security scan catches known vulnerabilities before they're packaged, integration tests catch problems that only show up when components interact, the approval gate ensures a human has visibility before production is touched, and monitoring confirms the deployment is actually working in the real environment, not just that the pipeline itself exited successfully.

Azure Pipelines and DevSecOps

Security doesn't have to be a separate phase that happens after development — it can be built directly into the pipeline:

  • Dependency scanning — automatically checking third-party packages for known vulnerabilities

  • Secret scanning — catching accidentally committed credentials before they reach a shared branch

  • Static analysis — automated code-level security and quality checks

  • Container scanning — checking built container images for vulnerabilities before they're pushed to a registry

  • Infrastructure-as-code checks — validating that infrastructure definitions (Terraform, Bicep, ARM) don't introduce insecure configurations

  • Security gates — failing the pipeline outright if a scan finds something above an agreed severity threshold

  • Least privilege — applied throughout, from service connections to environment permissions

The underlying principle: security should be part of the pipeline, not an afterthought bolted on before a release. Catching a vulnerable dependency in a CI run costs a few minutes; catching it after production deployment costs considerably more.

Azure Pipelines and Infrastructure as Code

Pipelines can manage infrastructure changes with the same rigor as application code, using tools like ARM templates, Bicep, or Terraform.

A typical flow:

Infrastructure code → Validate → Plan → Approval → Apply

  • Validate — check the infrastructure definition is syntactically and structurally correct

  • Plan — generate a preview of exactly what will change (resources created, modified, or destroyed) without applying anything yet

  • Approval — a human reviews the plan output before anything touches real infrastructure, especially for production

  • Apply — the reviewed, approved change is actually executed against the target environment

Running the "plan" step and gating "apply" behind an explicit approval — rather than applying infrastructure changes automatically and unreviewed — is the standard safeguard against an infrastructure-as-code pipeline accidentally deleting or misconfiguring something in production.

Azure Pipelines and Observability

A deployment reporting "succeeded" only means the deployment mechanism itself completed without error — it doesn't mean the application is actually healthy. Post-deployment validation closes that gap:

  • Logs — application and infrastructure logs, reviewed or monitored after deployment

  • Metrics — performance and health metrics compared against expected baselines

  • Application monitoring — tools that track real application behavior in production

  • Health checks — automated endpoints or checks confirming the application is actually responding correctly

  • Alerts — automatic notification when something goes wrong post-deployment

  • Rollback decisions — a defined process for deciding when a deployment's real-world health data means it should be rolled back, rather than leaving that judgment call ad hoc

This is why smoke tests and post-deployment health checks are worth including as pipeline steps in their own right, not just as a manual "check it later" task — deployment success and application success are related but genuinely different things.

Frequently Asked Questions

What is an Azure Pipeline? An Azure Pipeline is an automated workflow, defined mostly in YAML, that builds, tests, and deploys code. It's part of the Azure DevOps suite.

What is Azure Pipelines used for? Automating the build, test, and deployment process for software projects, replacing manual build/deploy steps with a consistent, repeatable process.

Is Azure Pipelines part of Azure DevOps? Yes — <cite index="18-1">Azure Pipelines is the CI/CD component of the broader Azure DevOps suite</cite>, alongside Azure Repos, Boards, Artifacts, and Test Plans.

What is CI/CD? Continuous Integration (frequent automated build/test on every code change) combined with Continuous Delivery or Continuous Deployment (automatically preparing, and optionally automatically shipping, validated changes to production).

What is the difference between CI and CD? CI focuses on building and testing changes automatically as they're merged. CD focuses on what happens after that — either keeping the change ready to deploy (Continuous Delivery, with a manual gate) or deploying it automatically (Continuous Deployment, no manual gate).

What is an Azure YAML pipeline? A pipeline defined as a .yml file checked into your repository, describing triggers, stages, jobs, and steps as code.

What is an Azure Pipelines agent? The machine — Microsoft-hosted or self-hosted — that actually executes a pipeline's jobs.

What is a pipeline stage? A major phase of a pipeline, such as Build, Test, or Deploy, that can contain multiple jobs and can depend on other stages.

What is a pipeline job? A set of steps that run together on a single agent.

What is a pipeline task? A reusable, pre-built action (provided by Azure DevOps or an extension) used within a step to perform a specific operation.

What is an artifact in Azure Pipelines? The packaged output of a build stage — a compiled binary, container image reference, or similar — published so later stages can deploy the exact same tested output.

What is an Azure DevOps environment? A named deployment target (like Staging or Production) that tracks deployment history and can enforce approvals and checks before a stage is allowed to deploy to it.

How do Azure Pipelines deploy applications? Through deployment jobs referencing a target environment, using tasks or scripts that push a build artifact to that environment — Azure services, other clouds, containers, or on-premises servers.

Can Azure Pipelines deploy to AWS or other clouds? Yes — Azure Pipelines is not limited to Azure targets; it can deploy anywhere reachable through an appropriate task, script, or service connection.

Is Azure Pipelines free? Azure DevOps offers free tiers with usage limits (parallel jobs, storage); costs scale with usage beyond those limits. Check the official Azure DevOps pricing page for current details.

What is the difference between Azure Pipelines and GitHub Actions? Azure Pipelines integrates natively with the broader Azure DevOps suite and supports multiple source control systems; GitHub Actions is built directly into GitHub and is generally considered easier to pick up for teams already working there.

What is the difference between Azure Pipelines and Jenkins? Azure Pipelines is a hosted service with lower operational overhead; Jenkins is self-managed, open source, and offers a very large plugin ecosystem in exchange for more infrastructure responsibility.

Are Azure Pipelines suitable for enterprise applications? Yes — environments, approvals, checks, service connections, and templates are specifically designed to support governance, security, and scale requirements common in enterprise settings.

How do I create my first Azure Pipeline? Create an Azure DevOps project, connect a repository, create a new pipeline, choose YAML, generate or write the pipeline file, and run it — then iteratively add tests and deployment stages.

How do I secure secrets in Azure Pipelines? Use secret variables or Azure Key Vault integration rather than plain YAML variables, scope service connections to least privilege, and avoid printing secret values in script output.


Create a free website with Framer, the website builder loved by startups, designers and agencies.