Basics Lesson 4 of 16

CI/CD Concepts & Pipeline Types

Understand Continuous Integration, Continuous Delivery, and Continuous Deployment — the engine behind modern software delivery. Learn how Azure Pipelines executes work through agents, pools, jobs, and tasks, and compare YAML pipelines to Classic pipelines to choose the right approach for your team.

🧒 Simple Explanation (ELI5)

Imagine a toy car factory with a long assembly line. At one end, raw materials go in. At each station along the line, a worker does one thing — one cuts the metal, the next paints it, the next attaches the wheels, and the last one puts it in a box. The car moves automatically from station to station without anyone carrying it by hand.

Now imagine what happens if there's no assembly line. One person cuts the metal, walks across the factory, hands it to the painter, waits for the paint to dry, carries it to the wheel station… it's slow, error-prone, and someone eventually drops a car on the floor.

💡
Why Should You Care?

Without CI/CD, shipping software is like hand-carrying toy cars across the factory. With CI/CD, you get an automated assembly line that catches defects early, delivers faster, and lets your team focus on building features instead of manually running builds and copying files to servers.

🔧 Technical Explanation

CI vs CD: The Three Meanings

The acronym "CD" is frustratingly overloaded. Let's be precise:

TermWhat HappensHuman Approval?Risk Level
Continuous Integration (CI)Every commit triggers an automated build and test run. The result is a validated artifact (binary, container image, package).No — fully automaticLow — only builds and tests, doesn't deploy
Continuous Delivery (CD)CI + automated deployment to staging/pre-production. Production deployment is possible at any time but requires a manual approval gate.Yes — human approves production deployMedium — staging is automated, production is gated
Continuous Deployment (CD)CI + automated deployment all the way to production. Every commit that passes all tests goes live automatically.No — fully automatic to productionHigher — requires excellent test coverage and monitoring
Important

Most enterprise teams practice Continuous Delivery, not Continuous Deployment. A manual approval gate before production is the norm — it gives release managers, security teams, or product owners a final checkpoint. Continuous Deployment is more common at companies like Netflix or GitHub, where test suites are extremely comprehensive and monitoring can auto-roll back bad deploys.

Pipeline Execution Model in Azure DevOps

Azure Pipelines has a well-defined hierarchy for organizing and executing work:

Pipeline → Trigger → Stage → Job → Step → Task

Agents and Agent Pools

An agent is the compute that actually runs your pipeline jobs. It's a machine (or container) with the Azure Pipelines agent software installed. Agents are organized into pools.

AspectMicrosoft-Hosted AgentSelf-Hosted Agent
Who manages it?Microsoft — fully managed VMs spun up on demandYou — installed on your own VM, on-prem server, or container
OS optionsUbuntu (latest), Windows Server (latest), macOS (latest)Any OS that supports .NET — Windows, Linux, macOS
Fresh each run?Yes — each job gets a brand-new VM. No state persists between runs.No — the agent persists. You control cleanup between runs.
Pre-installed toolsComprehensive — Docker, Node, Python, Java, .NET, kubectl, Helm, Terraform, az CLI, and moreOnly what you install. Full control over tool versions.
Network accessPublic internet only. Cannot reach private VNets, on-prem networks, or internal resources without extra config.Full access to your network — databases, internal APIs, private registries, on-prem resources.
CostFree tier: 1 parallel job, 1800 minutes/month. Paid: $40/month per parallel job (unlimited minutes).Free tier: 1 free parallel job. Paid: $15/month per parallel job. You pay for the VM infrastructure separately.
MaintenanceZero — Microsoft patches, updates, and scales the VMsYou handle OS patches, agent updates, disk cleanup, and scaling
SpeedCold start: ~20-40 seconds to provision a new VM. No caching between runs (unless you use pipeline caching tasks).Near-instant start (agent already running). Supports persistent disk caching for faster builds.
SecurityIsolated VMs — no cross-tenant access. Destroyed after each job.Runs on your infra — you control security posture. Risk: if compromised, attacker has network access.
Best forMost teams, open-source projects, public-facing buildsPrivate network access required, large builds needing caching, compliance requirements for build infrastructure

Agent Capabilities

Each agent advertises capabilities — key-value pairs describing what the agent can do (e.g., node=18.x, docker=installed, Agent.OS=Linux). Pipelines specify demands — requirements that an agent must satisfy to run the job.

⚠️
Warning

If no agent in the pool matches the pipeline's demands, the job will queue indefinitely with the message "Waiting for an agent." Always verify agent capabilities match your pipeline demands. For Microsoft-hosted agents, capabilities are documented per image — check the microsoft/azure-pipelines-image-generation repository for the full list of pre-installed tools per image.

YAML Pipelines vs Classic Pipelines

Azure DevOps offers two ways to define pipelines:

YAML Pipelines — Pipeline-as-Code. You write the entire pipeline definition in a .yml file stored in your repository alongside your application code.

yaml
# azure-pipelines.yml — YAML Pipeline example
trigger:
  branches:
    include: [main]
  paths:
    exclude: [docs/*, README.md]

pool:
  vmImage: 'ubuntu-latest'

stages:
- stage: Build
  jobs:
  - job: BuildApp
    steps:
    - task: DotNetCoreCLI@2
      displayName: 'Restore packages'
      inputs:
        command: restore
        projects: '**/*.csproj'

    - task: DotNetCoreCLI@2
      displayName: 'Build'
      inputs:
        command: build
        projects: '**/*.csproj'
        arguments: '--configuration Release'

    - task: DotNetCoreCLI@2
      displayName: 'Run tests'
      inputs:
        command: test
        projects: '**/*Tests.csproj'

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

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

Classic Pipelines — GUI-based. You build the pipeline using a visual drag-and-drop editor in the Azure DevOps web portal. No YAML file needed.

Microsoft's Direction

Microsoft is investing heavily in YAML pipelines and has stated that YAML is the future of Azure Pipelines. Classic pipelines are still fully supported, but new features (like environments, deployment strategies, and template expressions) are YAML-only. For new projects, always start with YAML. Use Classic only when migrating legacy pipelines or when team members need a visual editor to get started.

📊 Pipeline Execution Model & Comparison

Pipeline Execution Hierarchy
Pipeline (azure-pipelines.yml)
Trigger (push to main)
Stage: Build
Stage: Test
Stage: Deploy-Staging
Stage: Deploy-Prod
Job: BuildApp
runs on Agent
Job: UnitTests
runs on Agent
Job: IntegTests
runs on Agent
Step: Restore
Step: Build
Step: Test
Step: Publish
YAML Pipeline vs Classic Pipeline
YAML Pipeline
📄 Defined in .yml file in repo
🔀 Version-controlled with Git
📝 Code review via PR
🔁 Templates & reuse
🚀 Full feature set
Classic Pipeline
🖱️ Defined in GUI editor
💾 Stored in Azure DevOps
👆 Click-to-configure tasks
📋 Limited reuse (task groups)
⚠️ No new features planned

📋 YAML Pipelines vs Classic Pipelines

DimensionYAML PipelinesClassic Pipelines
Definition formatCode — .yml file in the repositoryGUI — visual editor in Azure DevOps portal
Version controlFull Git history — diffs, blame, branches, PRsRevision history in Azure DevOps (limited, not Git)
Code reviewPipeline changes go through PR review like any code changeNo PR review — changes are saved instantly in the UI
BranchingPipeline definition travels with the branch — each branch can have a different pipelineSingle pipeline definition shared across all branches
Templates & reusePowerful template system — extends, parameters, cross-repo templatesTask Groups — limited reuse, no cross-project sharing
Multi-stageNative — build and deploy in a single YAML file with approval gatesSeparate Build and Release pipelines required
EnvironmentsFull support — environment keyword with deployment strategies (runOnce, rolling, canary)Not supported — uses Release pipeline deployment groups
Trigger typesCI triggers, PR triggers, scheduled triggers, pipeline resource triggers — all in YAMLCI triggers and scheduled triggers in UI. PR triggers limited.
Learning curveSteeper — requires YAML syntax knowledge, indentation disciplineEasier — form fields and dropdowns, no syntax to learn
PortabilityPipeline file can be copied to another repo or organizationLocked to the Azure DevOps project — cannot be exported easily
DebuggingUse system.debug: true variable for verbose logs. Full YAML validation in editor.Enable debug mode in pipeline variables. UI-based log inspection.
Microsoft investmentActive development — new features are YAML-first or YAML-onlyMaintenance mode — no new features planned
Recommended forAll new projects, teams practicing GitOps, enterprise-scaleLegacy projects, quick prototyping, teams new to CI/CD
💡
Tip

If your team currently uses Classic Pipelines, don't rush to migrate all at once. Start by creating new pipelines in YAML, then gradually migrate Classic pipelines during natural maintenance cycles. Azure DevOps provides an "Export to YAML" option on Classic Build pipelines (not Release pipelines) that can generate a starting YAML file.

🖥️ Microsoft-Hosted vs Self-Hosted Agents

FactorMicrosoft-Hosted ✅ Pros / ❌ ConsSelf-Hosted ✅ Pros / ❌ Cons
Setup✅ Zero setup — select a VM image and go❌ Manual install — download agent, configure, register with pool
Maintenance✅ Zero maintenance — Microsoft patches and updates VMs❌ You maintain OS patches, agent updates, disk space
Clean environment✅ Fresh VM every run — no leftover state from previous builds❌ Persistent environment — leftover files can cause "works on my agent" bugs
Build speed❌ Slower cold start (~20-40s). No cache between runs.✅ Instant start. Persistent disk caching — incremental builds are significantly faster.
Network access❌ Cannot reach private VNets, on-prem DBs, or internal APIs without workarounds✅ Full access to your private network, on-prem resources, databases, internal registries
Cost at scale❌ $40/month per parallel job. Costs increase linearly with parallelism.✅ $15/month per parallel job + your VM cost. Often cheaper at scale with reserved VMs.
Tool control❌ Limited — you get what Microsoft pre-installs. Can install tools at runtime but adds build time.✅ Full control — install exact tool versions, proprietary SDKs, licensed software
Security isolation✅ Isolated, ephemeral VMs — destroyed after each job. No cross-tenant risk.❌ Persistent machine — if compromised, attacker has ongoing network access. Requires hardening.
Scalability✅ Auto-scales — Microsoft manages capacity. Buy more parallel jobs as needed.❌ Manual scaling — add more VMs yourself. Can use VMSS-based agent pools for auto-scaling.
Free tier1 parallel job, 1800 minutes/month (public projects get 10 free parallel jobs)1 free parallel job (unlimited minutes — you pay only for your own VM)

🛠️ Hands-on: Your First Pipeline

Follow these steps to create a YAML pipeline from scratch, run it, inspect the agent logs, and optionally configure a self-hosted agent.

Prerequisites

Step 1: Create a Minimal YAML Pipeline (Hello World)

In the root of your repo, create a file named azure-pipelines.yml:

yaml
# azure-pipelines.yml — Hello World pipeline
trigger:
  branches:
    include:
      - main

pool:
  vmImage: 'ubuntu-latest'

steps:
- script: echo "Hello, Azure Pipelines!"
  displayName: 'Run a one-line script'

- script: |
    echo "Pipeline ID:   $(Build.BuildId)"
    echo "Repository:    $(Build.Repository.Name)"
    echo "Branch:        $(Build.SourceBranchName)"
    echo "Commit:        $(Build.SourceVersion)"
    echo "Agent name:    $(Agent.Name)"
    echo "Agent OS:      $(Agent.OS)"
    echo "Working dir:   $(System.DefaultWorkingDirectory)"
  displayName: 'Print pipeline variables'

Commit and push to main:

bash
git add azure-pipelines.yml
git commit -m "ci: add hello world pipeline"
git push origin main

Step 2: Create the Pipeline in Azure DevOps

  1. Navigate to Pipelines → Pipelines → New Pipeline
  2. Select your repository source (Azure Repos Git)
  3. Select your repository
  4. Choose "Existing Azure Pipelines YAML file"
  5. Select branch main and path /azure-pipelines.yml
  6. Click Run
⚠️
Free Tier Note

If this is a new Azure DevOps organization, you may need to request a free parallelism grant. Microsoft disabled automatic free tiers for new orgs to prevent abuse. Fill out the form at aka.ms/azpipelines-parallelism-request. Approval usually takes 2-3 business days. Until then, your pipeline will queue with "No hosted parallelism has been purchased or granted."

Step 3: View the Run and Inspect Agent Logs

  1. Navigate to Pipelines → Pipelines and click on your pipeline
  2. Click the latest run to see the stages and jobs
  3. Click the job (e.g. "Job") to expand the step-by-step log output
  4. Click on each step to see its detailed log:
    • "Initialize job" — shows agent provisioning, capabilities, and variable resolution
    • "Checkout" — shows the Git clone operation (repo URL, branch, commit SHA)
    • "Run a one-line script" — shows your echo output
    • "Print pipeline variables" — shows the resolved system variables
    • "Post-job: Checkout" — cleanup step
text
Key things to look for in the agent logs:
──────────────────────────────────────────
✅ Agent name → e.g., "Hosted Agent" (Microsoft-hosted)
✅ Agent.OS → "Linux" (ubuntu-latest image)
✅ Agent.Version → the agent software version
✅ System.DefaultWorkingDirectory → /home/vsts/work/1/s
✅ Build.SourceBranchName → "main"
✅ Build.SourceVersion → the Git commit SHA
✅ Exit code of each step → 0 = success, non-zero = failure

To enable verbose debug logging for troubleshooting, add a pipeline variable:

yaml
variables:
  system.debug: true

Step 4: Create a Custom Agent Pool

  1. Navigate to Organization Settings → Agent Pools
  2. Click "Add pool"
  3. Select Pool type: Self-hosted
  4. Name it (e.g., my-linux-pool)
  5. Optionally grant access to all pipelines
  6. Click Create

Step 5: Configure a Self-Hosted Agent

Set up a self-hosted agent on a Linux VM (or WSL):

bash
# 1. Create a PAT (Personal Access Token) in Azure DevOps
#    User Settings → Personal Access Tokens → New Token
#    Scope: Agent Pools (Read & Manage)

# 2. Download the agent
mkdir ~/myagent && cd ~/myagent
curl -fkSL -o vsts-agent-linux-x64.tar.gz \
  https://vstsagentpackage.azureedge.net/agent/3.248.0/vsts-agent-linux-x64-3.248.0.tar.gz
tar zxvf vsts-agent-linux-x64.tar.gz

# 3. Configure the agent
./config.sh \
  --url https://dev.azure.com/{your-organization} \
  --auth pat \
  --token {your-pat-token} \
  --pool my-linux-pool \
  --agent my-agent-01 \
  --acceptTeeEula

# 4. Run the agent interactively (for testing)
./run.sh

# 5. Install as a systemd service (for production)
sudo ./svc.sh install
sudo ./svc.sh start
sudo ./svc.sh status

Now update your pipeline to use the self-hosted pool:

yaml
# Use self-hosted agent pool instead of Microsoft-hosted
pool:
  name: 'my-linux-pool'
  demands:
    - Agent.OS -equals Linux

steps:
- script: |
    echo "Running on self-hosted agent: $(Agent.Name)"
    echo "Agent machine name: $(Agent.MachineName)"
    hostname
    uname -a
  displayName: 'Verify self-hosted agent'
💡
Tip

For a quick local test, you can run the self-hosted agent in a Docker container instead of setting up a full VM. Microsoft provides documentation for running the agent in Docker. This is great for local experimentation and disposable agents.

🐛 Debugging Scenarios

Scenario 1: "No hosted parallelism has been purchased or granted"

Symptom: Your pipeline is queued and never starts. The log shows: No hosted parallelism has been purchased or granted. To request a free parallelism grant, please fill out the following form: https://aka.ms/azpipelines-parallelism-request.

Root Cause: Since February 2021, new Azure DevOps organizations (especially those using free Microsoft-hosted agents) do not get automatic free parallel jobs. Microsoft introduced this restriction to prevent crypto-mining abuse of free CI/CD resources.

Fix:

text
Check your current parallelism:
Organization Settings → Pipelines → Parallel jobs
─────────────────────────────────────────────────
Microsoft-hosted: X of Y parallel jobs (0 = not granted)
Self-hosted:      X of Y parallel jobs (1 free by default)

Scenario 2: "No agent found in pool matching specified demands"

Symptom: Your pipeline job shows "Waiting for an agent" indefinitely, or fails with "No agent found in pool 'my-pool' which satisfies the specified demands."

Root Causes & Fixes:

bash
# Check agent status on the self-hosted machine
cd ~/myagent
sudo ./svc.sh status

# If stopped, restart it
sudo ./svc.sh start

# Check agent logs for errors
cat _diag/Agent_*.log | tail -50

Scenario 3: "YAML file not found" / "Pipeline YAML file azure-pipelines.yml was not found"

Symptom: The pipeline fails immediately with an error about the YAML file not being found in the repository.

Root Causes & Fixes:

bash
# Verify the file exists on the correct branch
git checkout main
ls -la azure-pipelines.yml

# If missed, create and push it
git add azure-pipelines.yml
git commit -m "ci: add pipeline YAML file"
git push origin main

Scenario 4: "Trigger not firing" — Pipeline doesn't run on push

Symptom: You push code to the repository, but the pipeline doesn't start. No run appears in the Pipelines section.

Root Causes & Fixes:

💡
Debugging Tip

To check why a pipeline didn't trigger, look at the pipeline's Runs page. If there's truly no run, the trigger didn't fire. Try manually running the pipeline (Run pipeline → select your branch) to verify the pipeline itself is valid. If the manual run works but automatic triggers don't, the issue is almost always in the trigger configuration or UI override.

🎯 Interview Questions

Beginner

Q: What is CI/CD and why is it important?

CI (Continuous Integration) is the practice of automatically building and testing code every time a developer pushes a commit. It catches bugs early — minutes after introduction rather than weeks later during manual testing. CD (Continuous Delivery) extends CI by automatically deploying validated code to staging environments, with a manual approval gate before production. CD (Continuous Deployment) goes further — every change that passes tests is automatically deployed to production. CI/CD is important because it: 1. Reduces risk — small, frequent releases are easier to debug than big-bang releases. 2. Speeds up delivery — features reach users faster. 3. Improves quality — automated tests catch regressions. 4. Frees developers — no manual build/deploy rituals.

Q: What is the difference between Continuous Delivery and Continuous Deployment?

Continuous Delivery means every commit is automatically built, tested, and deployed to a staging/pre-production environment. The code is always in a deployable state. However, the final deployment to production requires a manual approval — a human (release manager, product owner) clicks "approve" to push to prod. Continuous Deployment removes that manual gate — every commit that passes all automated tests is deployed to production automatically, with no human intervention. The key difference is the production gate: Delivery has one, Deployment doesn't. Most enterprises use Continuous Delivery because they want a human checkpoint before production. Continuous Deployment requires extremely high test coverage, robust monitoring, and automated rollback capabilities.

Q: What is an Azure Pipelines agent?

An agent is the compute resource that executes your pipeline jobs — it's the machine that actually runs the build, test, and deploy commands. Azure Pipelines offers two types: Microsoft-hosted agents are fully managed VMs provisioned on demand by Microsoft. You choose an OS image (Ubuntu, Windows, macOS), and you get a fresh VM for each job. They come with pre-installed tools (Docker, Node, Python, .NET, etc.) and require zero maintenance. Self-hosted agents are machines you own and manage — on-premises servers, cloud VMs, or containers. You install the Azure Pipelines agent software on them. They provide faster builds (persistent caching), network access to private resources, and full control over installed tools. Agents are organized into pools, and pipelines specify which pool to use.

Q: What is the hierarchy of a YAML pipeline in Azure DevOps?

The hierarchy from top to bottom is: Pipeline (the top-level YAML file) → Trigger (what event starts it — push, PR, schedule) → Stages (logical boundaries like Build, Test, Deploy-Staging, Deploy-Prod) → Jobs (a unit of work that runs on one agent — each job gets a fresh workspace) → Steps (individual operations within a job, run sequentially) → Tasks (pre-built reusable actions like DotNetCoreCLI@2 or inline scripts). Stages run sequentially by default but can be parallelized. Jobs within a stage can run in parallel on different agents. Steps always run sequentially within a job. For simple pipelines, you can omit stages and jobs — just define steps directly, and Azure DevOps wraps them in an implicit single stage and single job.

Q: What is the difference between a YAML pipeline and a Classic pipeline?

YAML pipelines are defined as code in a .yml file stored in your Git repository. They're version-controlled, support PR reviews, allow branching (each branch can have a different pipeline), support powerful templates for reuse, and get all new Azure Pipelines features. Classic pipelines are configured through a visual GUI editor in the Azure DevOps web portal. The definition is stored in Azure DevOps (not in your repo). They're easier for beginners (no YAML syntax to learn) but lack version control, PR reviews, and the advanced template system. Microsoft is investing in YAML pipelines and has stopped adding new features to Classic. For new projects, always choose YAML. Classic is suitable only for legacy projects or teams transitioning from manual CI/CD.

Intermediate

Q: Compare Microsoft-hosted and self-hosted agents. When would you choose each?

Microsoft-hosted: Zero setup, zero maintenance, fresh VM per run (no leftover state), pre-installed tools. Choose when: your build doesn't need private network access, you want zero ops overhead, and cold-start time (~20-40s) is acceptable. Ideal for most open-source projects and teams without strict build-infra requirements. Self-hosted: You manage the machine. Choose when: 1. You need private network access (on-prem databases, internal registries, VPNs). 2. Your builds are slow and need persistent disk caching (large monorepos, C++ builds). 3. You need specific software that's expensive to install each run (proprietary SDKs, GPU drivers). 4. Compliance requires builds to run on company-controlled infrastructure. 5. You want cheaper parallelism at scale ($15/month vs $40/month). The tradeoff: self-hosted agents need ongoing maintenance (OS patches, disk cleanup, agent updates) and security hardening.

Q: What are agent capabilities and demands? How do they interact?

Capabilities are key-value pairs that describe what an agent can do. There are two types: System capabilities are auto-detected by the agent software — OS version, installed tools, environment variables (e.g., Agent.OS=Linux, node=18.17.0). User capabilities are manually added by an admin (e.g., GPU=true, region=us-east). Demands are requirements specified in the pipeline YAML that an agent must satisfy to run the job. Syntax: demands: [Agent.OS -equals Linux, docker]. The pipeline scheduler matches demands against capabilities — only agents that satisfy all demands are eligible. If no agent matches, the job queues indefinitely. This mechanism is critical for self-hosted pools where agents may have different configurations. Microsoft-hosted agents have a fixed, well-documented set of capabilities per VM image.

Q: Explain the pipeline trigger types available in Azure DevOps YAML pipelines.

Azure DevOps YAML pipelines support four trigger types: 1. CI trigger (trigger:): Fires on code pushes to specified branches. Supports branch filters (include/exclude), path filters (only trigger when specific files change), and tag filters. 2. PR trigger (pr:): Fires when a pull request is created or updated targeting specified branches. Runs a validation build to check the PR before merging. 3. Scheduled trigger (schedules:): Fires on a cron schedule (e.g., nightly builds at 2 AM). Uses cron syntax with timezone support. Can be configured to run only if source code changed since last run. 4. Pipeline resource trigger (resources: pipelines:): Fires when another pipeline completes — enabling pipeline chaining (e.g., deploy pipeline triggers after build pipeline finishes). Each trigger type can be combined in a single YAML file. Setting trigger: none disables automatic CI triggers, requiring manual runs.

Q: What is a multi-stage YAML pipeline and how does it unify build and release?

A multi-stage YAML pipeline defines the entire CI/CD workflow — build, test, and multiple deployment environments — in a single YAML file. Before multi-stage pipelines, Azure DevOps required separate Build pipelines (CI) and Release pipelines (CD). Multi-stage YAML unifies them: Stage 1: Build — compile, unit test, publish artifact. Stage 2: Deploy to Dev — auto-deploy to development environment. Stage 3: Deploy to Staging — deploy to staging, run integration tests. Stage 4: Deploy to Production — deploy to prod with a manual approval gate using environment with approvals configured. Stages use dependsOn to define execution order and condition for conditional execution. The entire workflow is version-controlled, reviewable via PRs, and consistent across branches. This replaces the Classic Release pipeline's visual stage designer.

Q: How do you enable verbose logging in Azure Pipelines for debugging?

There are two ways: 1. Pipeline variable: Add system.debug: true to the variables: section of your YAML file. This enables verbose output for all tasks — they log detailed diagnostic information including input parameters, environment variables, and internal operations. 2. Queue-time variable: When manually running a pipeline, click "Variables" and add system.debug = true. This enables debug logging for that single run without modifying the YAML file — useful for one-off troubleshooting. Additionally, you can use ##vso[task.setVariable] logging commands in scripts to set output variables, and ##vso[task.logissue] to surface warnings/errors. The agent also writes diagnostic logs to the _diag/ folder on the machine, which you can download from the pipeline run's logs page via "Download logs" button.

Scenario-Based

Q: Your team's pipeline takes 45 minutes. Developers complain about slow feedback. How do you speed it up?

Investigation: First, analyze which stages/jobs/steps take the longest — open a pipeline run and check step durations. Then apply targeted optimizations: 1. Parallelize jobs: Run unit tests, integration tests, and linting as separate parallel jobs across multiple agents instead of sequential steps. 2. Pipeline caching: Use the Cache@2 task to cache NuGet packages, npm modules, or Docker layers between runs — avoiding re-downloads. 3. Incremental builds: Switch to self-hosted agents where build artifacts persist on disk, enabling incremental compilation. 4. Optimize Docker builds: Use multi-stage Dockerfiles and layer caching. Only rebuild layers that changed. 5. Test splitting: Split your test suite across parallel agents and merge results — the PublishTestResults task supports this. 6. Reduce scope: Use path-based triggers to only run the full pipeline when relevant code changes (skip for docs-only commits). 7. Pre-built base images: Use a custom Docker agent image with all tools pre-installed instead of installing them during each run.

Q: You need your pipeline to access an on-premises SQL Server database during integration tests. Microsoft-hosted agents can't reach it. What do you do?

Option 1 — Self-hosted agent on the corporate network: Install a self-hosted agent on a VM that has network access to the on-prem SQL Server. Register it in a dedicated pool (e.g., corp-network-pool). Update your pipeline to use pool: name: 'corp-network-pool'. This is the simplest and most common approach. Option 2 — Self-hosted agent with VPN/ExpressRoute: If the agent runs in Azure (not on-prem), configure Azure VPN Gateway or ExpressRoute to connect the Azure VNet to the corporate network. Place the self-hosted agent VM in that VNet. Option 3 — Azure DevOps Agent behind a proxy: If network policies require a proxy, configure the agent with --proxyurl during setup. Option 4 — Hybrid approach: Run build/unit-test stages on Microsoft-hosted agents (fast, no network needed), then run the integration-test stage on a self-hosted agent that has network access. This minimizes self-hosted infra while solving the network problem.

Q: A developer pushes to main, but the pipeline doesn't trigger. Walk through your debugging steps.

Step-by-step debugging: 1. Check pipeline status: Is the pipeline disabled? Go to Pipelines → [pipeline] → ⋮ → Settings. Ensure "Disabled" is not toggled on. 2. Check trigger config: Open the YAML file. Does the trigger: section include main? If trigger: none, no automatic triggers fire. 3. Check UI override: Go to Pipeline → Edit → ⋮ → Triggers. If "Override the YAML continuous integration trigger from here" is checked, the UI settings override YAML — it might have different branch filters. 4. Check path filters: If the trigger has paths: exclude: [docs/*, README.md] and the commit only touched docs/, the pipeline correctly won't trigger. 5. Check YAML syntax: A malformed trigger section can silently break. Use the pipeline editor to validate YAML. 6. Check service connection: If using an external repo (GitHub), the webhook may be broken. Re-authorize the service connection. 7. Test manually: Run the pipeline manually with the same branch. If that works, the pipeline itself is valid — the issue is purely in the trigger configuration.

Q: Your organization is migrating from Jenkins to Azure Pipelines. You have 50 Jenkins jobs. What's your migration strategy?

Phase 1 — Inventory & Classify: List all 50 Jenkins jobs. Classify by type: CI builds, CD deployments, scheduled jobs, utility scripts. Identify dependencies between jobs (job chaining). Document each job's triggers, steps, credentials, and artifact flows. Phase 2 — Infrastructure: Set up Azure DevOps organization, projects, repos, service connections (to Azure, Docker registries, Kubernetes clusters). Migrate secrets from Jenkins credential store to Azure DevOps variable groups or Azure Key Vault. Decide agent strategy: Microsoft-hosted for most builds, self-hosted for jobs that need private network access. Phase 3 — Migrate (incremental): Start with simple CI jobs. Convert Jenkinsfile stages to YAML stages. Map Jenkins plugins to Azure Pipelines tasks (many have direct equivalents). Run the Jenkins and Azure pipeline in parallel to validate output matches. Phase 4 — Complex jobs: Migrate multi-branch pipelines, parameterized builds, and CD jobs. Use YAML templates to replace Jenkins shared libraries. Phase 5 — Cutover: Disable Jenkins jobs one by one as Azure equivalents are validated. Keep Jenkins read-only for 30 days, then decommission.

Q: Your YAML pipeline runs fine on the main branch but fails on feature branches with "access denied" errors when deploying to staging. What's happening?

Root cause: Azure DevOps environment approvals and checks (or pipeline permissions) are restricting which branches can deploy. Several mechanisms can cause this: 1. Environment branch control: The "staging" environment may have a check configured to only allow deployments from the main branch. Go to Pipelines → Environments → staging → Approvals and checks and look for a "Branch control" check. 2. Service connection authorization: The service connection used for deployment may be authorized only for the main branch pipeline. Go to Project Settings → Service connections → [connection] → Security and check pipeline permissions. 3. Variable group restricted access: If the pipeline uses a variable group with secrets, the group may only be authorized for specific pipelines or branches. Fix: For feature-branch deployments to staging (which is a valid use case), add the feature branch pattern to the branch control check's allowed list, or create a separate "dev" environment with relaxed branch controls. Never relax production environment branch controls.

🌍 Real-World Use Case

A Fintech Startup's Journey from Manual Deploys to CI/CD

Consider a fintech startup building a payments API — 8 developers, a Node.js backend, a React frontend, deployments to Azure Kubernetes Service, and a regulatory requirement for audit trails on every production change.

The problem (before CI/CD):

What they built with Azure Pipelines:

Results after 6 months:

💡
Key Takeaway

CI/CD isn't just about automation — it's about confidence. When you know that every commit is built, tested, and deployable, you can ship faster with less risk. Azure Pipelines provides the execution engine (agents), the workflow model (stages, jobs, steps), and the governance layer (approvals, branch controls, audit logs) to make this a reality for teams of any size.

📝 Summary

ConceptKey Takeaway
Continuous IntegrationEvery commit triggers an automated build and test. Bugs are caught in minutes, not weeks. The output is a validated artifact.
Continuous DeliveryCI + automated deployment to staging. Production requires manual approval. This is what most enterprises practice.
Continuous DeploymentCI + automated deployment all the way to production. No human gate. Requires excellent tests and monitoring.
Pipeline HierarchyPipeline → Trigger → Stages → Jobs → Steps → Tasks. Stages are logical boundaries. Jobs run on agents. Steps are sequential.
Microsoft-Hosted AgentsZero maintenance, fresh VM per run, pre-installed tools. Best for most teams. Limited: no private network access.
Self-Hosted AgentsFull network access, persistent caching, tool control. Best for private networks and large builds. You maintain the machine.
Agent Capabilities & DemandsCapabilities describe what an agent has. Demands describe what a pipeline needs. Scheduler matches them to find compatible agents.
YAML PipelinesPipeline-as-Code in .yml file. Version-controlled, PR-reviewable, full feature set. Microsoft's primary investment.
Classic PipelinesGUI-based visual editor. No version control, no new features. Use only for legacy or quick prototyping.
When to use whichNew projects → YAML always. Existing Classic → migrate incrementally. Need private network → self-hosted agent. Otherwise → Microsoft-hosted.
← Back to Azure DevOps Course