0Pricing
Cloud & IT Cert Prep · Lesson

Building a CI Pipeline with Azure Pipelines

Define a YAML pipeline that triggers on pull requests, runs unit tests, and produces a build artifact, then review test results and code coverage in the portal.

Building a CI Pipeline with Azure Pipelines is a free Cloud & IT Cert Prep lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Cloud & IT Cert Prep learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

What Is Continuous Integration?

Continuous Integration (CI) is the practice of frequently merging code changes into a shared branch, with each merge automatically triggering a build and test run. The goal is to detect integration failures early — before they compound into large, hard-to-fix problems. A good CI pipeline builds the code, runs unit tests, measures code coverage, performs static analysis, and produces a deployable artifact in a few minutes. Azure Pipelines provides the automation engine for this practice.

YAML Pipeline Structure

Azure Pipelines CI pipelines are defined in a azure-pipelines.yml file at the root of your repository. The YAML file specifies triggers (when to run), a pool (which agent type to use), and a hierarchy of stages, jobs, and steps. Stages run sequentially by default. Jobs within a stage run in parallel by default. Steps within a job run sequentially. This structure gives you fine-grained control over the pipeline's execution flow.

# azure-pipelines.yml skeleton
trigger:
  branches:
    include:
    - main
    - 'feature/*'
  paths:
    exclude:
    - docs/**
    - '*.md'

pool:
  vmImage: ubuntu-latest

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

stages:
- stage: CI
  displayName: 'Build and Test'
  jobs:
  - job: Build
    displayName: 'Build Application'
    steps: []

Trigger Configuration

Azure Pipelines supports several trigger types. Branch triggers run the pipeline when code is pushed to specified branches. Pull request triggers (PR triggers) run when a PR is opened or updated against target branches — essential for validating code before merging. Scheduled triggers run at a fixed time (e.g., nightly builds). Pipeline triggers chain pipelines together. Specify trigger: none to disable automatic runs and only allow manual execution.

# Branch trigger
trigger:
  branches:
    include: [main, develop]

# Pull request trigger
pr:
  branches:
    include: [main]
  autoCancel: true  # Cancel previous runs when PR is updated

# Scheduled trigger (nightly build at 02:00 UTC)
schedules:
- cron: '0 2 * * *'
  displayName: 'Nightly Build'
  branches:
    include: [main]
  always: true  # Run even if no new commits

Steps: Scripts and Tasks

Pipeline steps are either scripts (bash or PowerShell commands) or tasks (pre-built, parameterised units from the Azure DevOps marketplace). Tasks like NodeTool@0, DotNetCoreCLI@2, and Maven@3 encapsulate common build operations. Use displayName on every step for readable pipeline logs. Each step runs in sequence and the pipeline fails if any step exits with a non-zero code, unless you set continueOnError: true.

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

- script: npm ci
  displayName: 'Install dependencies (clean install)'

- script: npm run lint
  displayName: 'Run ESLint'

- script: npm run build
  displayName: 'Build production bundle'

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

Publishing Test Results

After running tests, publish the results to Azure DevOps using the PublishTestResults task. Azure Pipelines parses JUnit, NUnit, XUnit, or VSTest result files and displays pass/fail counts, test run duration, and individual test details in the pipeline run UI. Test history is tracked over time so you can spot flaky tests and regressions. This is essential for code quality visibility across the team.

# Example: Node.js project with Jest tests
steps:
- script: npm test -- --ci --reporters=jest-junit
  displayName: 'Run tests with JUnit reporter'
  env:
    JEST_JUNIT_OUTPUT_DIR: '$(Agent.TempDirectory)/test-results'

- task: PublishTestResults@2
  displayName: 'Publish test results'
  inputs:
    testResultsFormat: JUnit
    testResultsFiles: '$(Agent.TempDirectory)/test-results/**/*.xml'
  condition: succeededOrFailed()  # Publish even if tests fail

Publishing Code Coverage

Publish code coverage reports so Azure Pipelines displays coverage percentages and trends in the pipeline UI. The PublishCodeCoverageResults task accepts Cobertura or JaCoCo format reports. Pair this with branch coverage gates — configure a minimum coverage threshold and fail the build if coverage drops below it. Coverage trends help identify when new code is being added without corresponding tests.

# Jest + coverage
- script: npm test -- --ci --coverage --coverageReporters=cobertura
  displayName: 'Run tests with coverage'

- task: PublishCodeCoverageResults@1
  displayName: 'Publish code coverage'
  inputs:
    codeCoverageTool: Cobertura
    summaryFileLocation: '$(System.DefaultWorkingDirectory)/coverage/cobertura-coverage.xml'
    reportDirectory: '$(System.DefaultWorkingDirectory)/coverage'

Pipeline Variables and Variable Groups

Store pipeline configuration in variables defined at the pipeline, stage, or job level in YAML. For sensitive values (API keys, passwords), use secret variables — set them in the pipeline library (UI) or variable groups and reference them in YAML. Variable groups are reusable collections of variables shared across multiple pipelines. Link a variable group to Azure Key Vault to automatically sync secrets from Key Vault into pipeline variables.

# Reference a variable group in a pipeline
variables:
- group: 'Production-Secrets'  # Linked to Azure Key Vault
- name: buildConfiguration
  value: Release

# Use a variable
steps:
- script: echo 'Building $(buildConfiguration) configuration'
- script: az webapp deploy --src-path drop.zip
  env:
    AZURE_SUBSCRIPTION_ID: $(AZURE_SUBSCRIPTION_ID)  # From Key Vault
    APP_API_KEY: $(APP_API_KEY)  # Secret, not printed in logs

Artifacts: Packaging Build Output

After a successful build, package the output into a pipeline artifact so downstream stages (like deployment) can access it. Use PublishPipelineArtifact to upload files from the build agent to Azure DevOps artifact storage. In a later stage or job, use DownloadPipelineArtifact to retrieve the artifact. This decouples the build job from deployment jobs, which can run on different agents or in different stages.

# Publish build artifact
- task: PublishPipelineArtifact@1
  displayName: 'Publish build artifact'
  inputs:
    targetPath: '$(System.DefaultWorkingDirectory)/dist'
    artifactName: webapp-drop
    publishLocation: pipeline

# In a later deployment job, download the artifact
- task: DownloadPipelineArtifact@2
  inputs:
    artifactName: webapp-drop
    targetPath: '$(Pipeline.Workspace)/drop'

- script: ls -la $(Pipeline.Workspace)/drop

Parallel Jobs for Faster Builds

Run independent tasks in parallel by defining multiple jobs within a stage. For example, run unit tests and security scanning simultaneously rather than sequentially. Parallel jobs require separate build minutes but can dramatically reduce total pipeline duration. Use the dependsOn property to make a job wait for one or more other jobs to complete before starting, creating a dependency graph within a stage.

stages:
- stage: CI
  jobs:
  - job: UnitTests
    displayName: 'Run unit tests'
    steps:
    - script: npm test

  - job: LintAndSecurity
    displayName: 'Lint and security scan'
    steps:
    - script: npm run lint
    - script: npm audit --audit-level=high

  - job: BuildArtifact
    displayName: 'Build and publish artifact'
    dependsOn: [UnitTests, LintAndSecurity]
    condition: succeeded('UnitTests') and succeeded('LintAndSecurity')
    steps:
    - script: npm run build

Build Validation with Branch Policies

Link your CI pipeline to a branch policy in Azure Repos so it runs automatically as a build validation check on pull requests targeting main. Merging is blocked until the pipeline passes. Combine multiple checks: CI pipeline must succeed, at least 2 reviewers must approve, all comments must be resolved, and a linked work item must exist. This creates a quality gate that makes it impossible to merge broken code into the main branch.

# Add build validation via CLI
az repos policy build create \
  --blocking true \
  --branch main \
  --branch-match-type exact \
  --build-definition-id <pipeline-id> \
  --display-name 'CI Build Validation' \
  --enabled true \
  --project MyProject \
  --repository-id <repo-id> \
  --queue-on-source-update-only true \
  --manual-queue-only false \
  --valid-duration 720  # Pipeline result expires after 12 hours

Reading Pipeline Run Results

After a pipeline run completes, review the results in the Azure DevOps portal. The Summary tab shows overall pass/fail and timing. The Tests tab lists all test results with filtering by outcome. The Code Coverage tab shows coverage percentage and highlights uncovered lines. Click any job to see the step-by-step log output. Failed pipelines show the failing step highlighted in red with the full error output for quick diagnosis.

# View pipeline run results via CLI
az pipelines runs list \
  --pipeline-ids <pipeline-id> \
  --project MyProject \
  --query '[].{id:id, status:status, result:result, startTime:startTime}' \
  -o table

# View logs from a specific run
az pipelines runs logs list \
  --run-id <run-id> \
  --project MyProject

Quick Check

Test your understanding of Microsoft Azure Fundamentals (AZ-900) concepts from this lesson.

Lesson Recap

In this lesson you learned: Azure Pipelines YAML defines CI pipelines with stages, jobs, and steps for build, test, and artifact creation, PR triggers and branch policies enforce quality gates that block merging broken code, and PublishTestResults and PublishCodeCoverageResults tasks make test quality visible across the team. Next up we explore continuous deployment to Azure.

Frequently asked questions

Is the “Building a CI Pipeline with Azure Pipelines” lesson free?

Yes — the full text of “Building a CI Pipeline with Azure Pipelines” is free to read here on the web, and the Cloud & IT Cert Prep course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Cloud & IT Cert Prep course, upgrade to CoddyKit PRO.

What will I learn in “Building a CI Pipeline with Azure Pipelines”?

Define a YAML pipeline that triggers on pull requests, runs unit tests, and produces a build artifact, then review test results and code coverage in the portal. You practise Cloud & IT Cert Prep with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Cloud & IT Cert Prep?

No prior experience is required. Cloud & IT Cert Prep on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Building a CI Pipeline with Azure Pipelines” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Cloud & IT Cert Prep lesson?

Yes. Every Cloud & IT Cert Prep lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Azure DevOps Services Overview
  2. Building a CI Pipeline with Azure Pipelines
  3. Continuous Deployment to Azure
  4. GitHub Actions on Azure
← Back to Cloud & IT Cert Prep