This is 50-55% of AZ-400, more than every other domain together, so most of your study time belongs here. Expect case studies and scenario questions on YAML pipeline structure, GitHub Actions workflows, agent choices, deployment strategies and IaC. Many questions ask you to complete or fix YAML, so read the syntax closely.
Package management
Azure Artifacts and GitHub Packages
| Azure Artifacts | GitHub Packages | |
|---|---|---|
| Package types | NuGet, npm, Maven, Python (PyPI), Cargo, Universal Packages | npm, NuGet, Maven/Gradle, RubyGems, and container images (Container registry, ghcr.io) |
| Organisation | Feeds (organisation or project scoped), with views | Packages attached to a repo or an organisation/user |
| Upstream sources | Yes: public (nuget.org, npmjs, Maven Central, PyPI) and other Azure Artifacts feeds | No upstream feature for most types |
| Auth | PAT, Entra ID credential provider, pipeline’s build service identity | GITHUB_TOKEN in Actions, PAT with read:packages/write:packages |
| Fit | Azure DevOps shops, internal libraries, caching public packages | GitHub-centric teams, publish alongside source |
Pick Azure Artifacts when you need upstream sources, views or Universal Packages (any file set). Pick GitHub Packages when the code and workflows live on GitHub and you want permissions that follow the repo.
Feeds, views and upstream sources
- Feed scope: project-scoped feeds follow the project’s visibility; organisation-scoped feeds can be shared. A feed has its own permissions: Reader, Collaborator (can also save packages from upstream), Contributor (publish), Owner.
- Views are filtered, read-only windows on a feed. Every feed has @Local, @Prerelease and @Release. Promote a package version to a view (for example
@Release) when it passes quality gates, so consumers subscribe to the view and only see approved packages. Promotion changes the view membership, not the package, and doesn’t rebuild. - Upstream sources let a feed resolve packages from public registries or other feeds. When you install a package via upstream, a copy is saved to the feed (“package caching”), so the build keeps working if the public source disappears. Upstream order and precedence: local packages win over upstream, and public package names that clash with private names are the dependency confusion risk. Mitigate by using an upstream behaviour setting that blocks the externally sourced version of a package name you publish internally.
- Retention policies on a feed delete old versions (maximum versions per package, days to keep recently downloaded packages).
- Connect to a feed:
nuget.config,.npmrc,settings.xml/pom.xmlandpip.confcarry the feed URL. In pipelines use the taskNuGetAuthenticate@1,npmAuthenticate@0,MavenAuthenticate@0,TwineAuthenticate@1orPipAuthenticate@1, which injects credentials from the build identity. The project’s Build Service account needs Contributor on the feed to publish.
- task: NuGetCommand@2
inputs:
command: push
packagesToPush: '$(Build.ArtifactStagingDirectory)/*.nupkg'
nuGetFeedType: internal
publishVstsFeed: 'MyProject/MyFeed'
Universal Packages carry any set of files with a name and SemVer version: az artifacts universal publish or the UniversalPackages@0 task.
Versioning strategy
| Scheme | Format | When |
|---|---|---|
| SemVer | MAJOR.MINOR.PATCH (optional -prerelease and +build). Major for breaking changes, minor for backward-compatible features, patch for backward-compatible fixes |
Libraries and APIs where consumers need to judge compatibility. Pre-release: 1.4.0-beta.2 |
| CalVer | Date-based, e.g. 2026.11.3 or 26.11 |
Products released on a schedule, where the date matters more than compatibility |
| Build number / run number | Increasing integer or $(Build.BuildId) |
Internal artifacts and traceability |
- Dependency version ranges:
^1.2.0(npm) accepts minor and patch updates,~1.2.0only patches, NuGet[1.0,2.0)is a range. Pin exact versions or use a lock file (package-lock.json,packages.lock.json) for reproducible builds. - Azure Pipelines
name(run number) can be a format such as$(Date:yyyyMMdd)$(Rev:.r)or1.0$(Rev:.r).$(Rev:.r)increments for repeats of the same prefix. - Tools such as GitVersion and Nerdbank.GitVersioning compute SemVer from branches and tags, so version comes from Git history rather than hand-edited files.
- Pipeline artifact versioning: give each pipeline artifact a version linked to the run (run number, commit SHA and tag), so every deployed artifact is traceable to a build. For Docker images, tag with both the immutable SHA or version and a moving tag (
latest,1.4). Deploy by digest for strict immutability.
Testing strategy
Types of tests
| Level | Scope | Run where |
|---|---|---|
| Local / developer | Unit tests, linters, analysers, pre-commit hooks, test containers | Dev machine and IDE |
| Unit | One function or class, no external dependencies, fast | CI build on every push |
| Integration | Components with real dependencies (database, queue, API) | CI against ephemeral services or a test environment |
| UI/end-to-end | Whole system through the UI or API (Playwright, Selenium) | Test stage after deploy |
| Load / performance | Throughput and latency under load (Azure Load Testing, JMeter, Locust) | Pre-production stage, a scheduled run |
| Security | SAST, DAST, dependency scan | See the security notes |
| Smoke | A few critical checks after each deployment | Right after deploy |
| Chaos / resilience | Inject faults (Azure Chaos Studio) | Staging |
Follow the test pyramid: many fast unit tests, fewer integration tests, few end-to-end tests. Shift tests left (earlier) where they are cheaper to fix.
Implementing tests in a pipeline
- Azure Pipelines:
VSTest@3,DotNetCoreCLI@2withcommand: test,Npm@1/script steps with JUnit output,PublishTestResults@2for results in JUnit, NUnit, xUnit, VSTest or TRX formats, shown in the Tests tab with trends and flaky test detection. Test tasks can run in parallel across agents using the multi-agent job strategy (parallel:) with VSTest test slicing (distribute by number of tests, past running time or assembly). - GitHub Actions: run the test command in a step; publish results with an action such as
dorny/test-reporteror upload them as artifacts. Use aservices:block to start database containers alongside a job. - Test agents: run UI tests on an agent with a display (a Microsoft-hosted
windows-latestagent works, or a self-hosted agent in interactive mode). Use Azure Test Plans for manual and exploratory testing. - Azure Load Testing runs JMeter scripts (or URL tests) at scale, with a pipeline task and fail criteria (for example average response time over 500 ms) that fail the run.
Code coverage
- Collect coverage with
--collect:"XPlat Code Coverage"(Coverlet) orVSTestsettings,jacoco(Java),istanbul/nyc(JavaScript) andcoverage.py. - Publish with
PublishCodeCoverageResults@2(supports Cobertura and JaCoCo). Azure Pipelines shows a Code Coverage tab, and PRs can show a coverage comment. - Enforce minimum thresholds in the build (a failing test command) or with a quality gate in SonarCloud/SonarQube or Codecov. In Azure Repos, a status check or build validation policy blocks a PR when coverage drops.
Quality and release gates
- Quality gates block progress based on measured quality: tests pass, coverage, static analysis (SonarQube quality gate), security scan results, a zero-critical-vulnerabilities rule.
- Release gates (classic releases) and checks on environments (YAML) test automatically before or after a deployment: Invoke Azure Function, Invoke REST API, Query Azure Monitor alerts, Query work items, plus Required template and Evaluate artifact checks. Gates re-evaluate on an interval until they pass or time out.
- Approvals, branch control, business hours and exclusive lock checks add governance. In GitHub use environment protection rules: required reviewers, wait timer, deployment branches and tags, and custom protection rules (GitHub Apps).
- Governance and security gates: require a Defender for Cloud / Advanced Security scan, a signed artifact or an SBOM before production.
Choosing and building pipelines
Azure Pipelines vs GitHub Actions
| Azure Pipelines | GitHub Actions | |
|---|---|---|
| Config | azure-pipelines.yml, or classic editor |
.github/workflows/*.yml |
| Hierarchy | Pipeline > stages > jobs > steps | Workflow > jobs > steps (jobs ordered by needs) |
| Triggers | trigger, pr, schedules, resources |
on: (push, pull_request, schedule, workflow_dispatch, workflow_call, repository_dispatch, release…) |
| Reuse | Templates (stage, job, step, variable) | Reusable workflows (workflow_call), composite actions, Marketplace actions |
| Environments | Environments with checks/approvals, deployment history, resource targets (VMs, Kubernetes) | Environments with protection rules and secrets |
| Agents | Microsoft-hosted, self-hosted, scale set agents | GitHub-hosted, self-hosted, larger runners |
| Strengths | Mature release management, test plans, boards, classic releases, deployment groups | Close to the code, vast action ecosystem, OIDC, fork and community friendliness |
Choose by where the code lives and what the team already uses. GitHub repo + Azure DevOps boards and release governance is common, so know how to bridge them.
GitHub and Azure Pipelines integration
- Install the Azure Pipelines GitHub App (from Marketplace) and create a pipeline that builds a GitHub repo. A GitHub service connection (OAuth, PAT or the GitHub App) authenticates.
- The app posts checks on pull requests and commits, so branch protection can require the Azure Pipelines check.
- PRs from forks are not given secrets by default. Use
Settings > Triggers > Pull request validationoptions to control forks. - Use
resources: repositories: - repository: selfandtype: githubwithendpoint:to check out other GitHub repos. - The reverse: trigger GitHub Actions workflows with
repository_dispatchfrom Azure Pipelines, or run Azure Pipelines from Actions using the Azure DevOps CLI or a REST call.
Agents and runners infrastructure
| Option | Notes |
|---|---|
| Microsoft-hosted agents | Fresh VM per job, patched by Microsoft, images such as ubuntu-latest, windows-latest, macos-latest. Time-limited (60 minutes for private projects on free tier, up to 360 minutes on paid parallel jobs, and no limit for self-hosted). No access to private networks |
| Self-hosted agents | You install the agent on a VM, container or physical machine. Use for private network access, special software or hardware, caching between runs, long jobs, no job time limit. You maintain patching and security. Agents connect outbound over HTTPS (443) to Azure DevOps, so no inbound firewall opening |
| Azure Virtual Machine Scale Set agents | Self-hosted agents that autoscale on a scale set, with ephemeral machines. Good balance of control and elasticity, cost only when running |
| Managed DevOps Pools | Microsoft-managed scale with your own VNet, images and sizes, and spot capacity. Less operational burden than scale set agents |
| GitHub-hosted runners | Ephemeral VMs, ubuntu-latest, windows-latest, macos-latest. Larger runners (more CPU/RAM, static IPs, Arm, GPU) cost more and are billed per minute |
| Self-hosted runners | Install the runner app, label it (runs-on: [self-hosted, linux, gpu]). Runner groups control which repos use them at organisation level. Actions Runner Controller (ARC) runs ephemeral runners in Kubernetes |
| Azure Private Networking for GitHub-hosted runners | GitHub-hosted runners injected into your Azure virtual network |
Design factors the exam lists:
- Cost: hosted agents are billed per parallel job (Azure DevOps) or per minute (GitHub). Self-hosted agents cost infrastructure but no per-minute licence fee beyond the free self-hosted parallel job (each self-hosted parallel job needs a licence beyond the first free one). Use scale sets or spot capacity for bursts.
- Tool selection: pre-installed software lists exist for hosted images. If you need something rare, install in the pipeline (
UseDotNet@2,NodeTool@0,actions/setup-node) or use a custom image or container job. - Licences: Azure DevOps has parallel jobs: one free Microsoft-hosted job (limited minutes per month; public projects get more) and one free self-hosted job, additional ones purchased per parallel job. Basic users license is 5 free per organisation.
- Connectivity: need to reach a private database, on-premises system or a private Azure endpoint -> self-hosted agents in the VNet or VNet-injected hosted agents. Firewalls allow only the Azure DevOps and GitHub service endpoints outbound.
- Maintainability: prefer ephemeral, image-built agents (scale sets, ARC) over long-lived pets. Automate image builds with Packer, patch the image, and reimage.
Agent pools group agents. An agent pool can be organisation-wide with project-level pools; use demands (pool: demands:) to pick an agent with certain capabilities. A deployment group (classic) installs an agent on target servers for deployment.
pool:
vmImage: ubuntu-latest # Microsoft-hosted
pool:
name: MyPool # self-hosted
demands:
- Agent.OS -equals Linux
Triggers
Azure Pipelines:
trigger:
branches:
include: [main, release/*]
exclude: [experimental/*]
paths:
include: [src/*]
exclude: [docs/*]
tags:
include: [v*]
batch: true # one run for several quick pushes
pr:
branches:
include: [main]
autoCancel: true
schedules:
- cron: "0 3 * * 1-5" # UTC
displayName: Weeknight build
branches:
include: [main]
always: false # only if changed
resources:
pipelines:
- pipeline: upstream
source: Build-Pipeline
trigger:
branches: [main]
trigger: nonedisables CI triggers. Pipeline resource triggers chain pipelines. Scheduled runs use UTC cron andalways: trueto run even without changes. For Azure Repos Git, PR validation is done through a branch policy (build validation), not thepr:keyword;pr:only works with GitHub and Bitbucket repos.- Path filters limit runs to changes in a folder (monorepos).
- Commit message
[skip ci]or***NO_CI***skips a run.
GitHub Actions:
on:
push:
branches: [main]
paths: ['src/**']
tags: ['v*']
pull_request:
branches: [main]
schedule:
- cron: '0 3 * * 1-5'
workflow_dispatch:
inputs:
environment:
type: choice
options: [dev, prod]
workflow_call:
workflow_run:
workflows: [Build]
types: [completed]
workflow_dispatch is a manual trigger, workflow_call makes the workflow reusable, workflow_run chains workflows, repository_dispatch accepts external API events.
YAML pipeline structure (Azure Pipelines)
trigger: [main]
variables:
- group: shared-vars # variable group
- name: buildConfiguration
value: Release
stages:
- stage: Build
jobs:
- job: BuildJob
pool: { vmImage: ubuntu-latest }
steps:
- script: dotnet build -c $(buildConfiguration)
- publish: $(Build.ArtifactStagingDirectory)
artifact: drop
- stage: DeployDev
dependsOn: Build
condition: succeeded()
jobs:
- deployment: DeployWeb
environment: dev
strategy:
runOnce:
deploy:
steps:
- download: current
artifact: drop
- task: AzureWebApp@1
inputs: { azureSubscription: 'svc-conn', appName: 'myapp', package: '$(Pipeline.Workspace)/drop/*.zip' }
- Stages are major divisions (Build, Test, Deploy) that run sequentially by default.
dependsOncreates a graph,dependsOn: []makes it run in parallel with others. - Jobs in a stage run in parallel by default (subject to parallel job capacity) and each gets its own agent and clean workspace. Use
dependsOnfor order. - Steps run in order on one agent:
script,bash,pwsh,task,checkout,download,publish. - A deployment job (
deployment:) targets an environment, records deployment history and supports strategiesrunOnce,rolling,canary, each with lifecycle hookspreDeploy,deploy,routeTraffic,postRouteTraffic,on: failure/success. - Job types: agent job, container job (
container:image to run steps in), server job (pool: server, runs on Azure DevOps itself with tasks such as Manual Validation, Invoke REST API, Azure Function), and deployment job. - Conditions:
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main')). Defaults tosucceeded().always(),failed(),canceled()andsucceededOrFailed()are the others. - Strategies:
matrixruns a job several times with variable sets,parallel: Nruns N copies,maxParallelcaps. This is used for multi-platform and sharded tests. - Execution order and parallelism: design with
dependsOnfor fan-out (parallel test jobs) and fan-in (a final job thatdependsOnall of them). - Predefined variables:
Build.BuildId,Build.SourceBranch,Build.SourceVersion,Pipeline.Workspace,Build.ArtifactStagingDirectory,System.AccessToken. - Runtime parameters (
parameters:) are typed and evaluated at queue time with${{ }}. Variables use macro$(var)at runtime,${{ variables.x }}at compile time, and$[ variables.x ]runtime expression. Compile-time expressions can’t see runtime values. - Output variables between jobs/stages:
echo "##vso[task.setvariable variable=x;isOutput=true]value"then refer viadependencies.<job>.outputs['<step>.x'](orstageDependenciesacross stages). - Pipeline artifacts (
publish/download,PublishPipelineArtifact@1) are for passing files within and across runs. Build artifacts is the older mechanism. Caching dependencies uses theCache@2task.
GitHub Actions workflow structure
name: ci
on: [push, pull_request]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node: [20, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: ${{ matrix.node }}, cache: npm }
- run: npm ci && npm test
deploy:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production
steps:
- run: echo deploy
- Jobs run in parallel unless
needsorders them. A job’soutputsflow to dependants throughneeds.<job>.outputs. permissionslimit theGITHUB_TOKEN(set the least needed; default may be read-only).concurrencygroups prevent overlapping runs andcancel-in-progresscancels older ones (typical for deployments to one environment).- Artifacts:
actions/upload-artifactanddownload-artifactpass files between jobs; cache withactions/cacheor thecache:option of setup actions. - Matrix strategy with
include/excluderuns combinations. - Pin actions to a full commit SHA for supply-chain safety.
- Action types: JavaScript, Docker container, composite (bundle steps in
action.yml). - Reusable workflows:
on: workflow_callin the callee; the caller usesuses: org/repo/.github/workflows/build.yml@mainwithwith:andsecrets: inherit. A reusable workflow is a whole job set; a composite action is a bundle of steps inside one job. - Workflow commands:
$GITHUB_OUTPUT,$GITHUB_ENV,$GITHUB_STEP_SUMMARY.
Complex pipelines
- Hybrid pipelines: deploy to on-premises and cloud in one pipeline. Self-hosted agents inside the datacentre reach local servers, while hosted agents deploy to Azure; or use environments with VM resources (register a server in an Environment) to deploy to it from a pipeline. For Kubernetes, register a cluster namespace as an environment resource.
- VM templates / images: build golden images with Packer (or Azure VM Image Builder and Azure Compute Gallery) in a pipeline so scale set agents and VMs start identical and patched.
- Self-hosted runners/agents: install, register with a PAT or Entra identity, run as a service, use ephemeral mode (
--ephemeralfor runners; one job then removed), keep images patched, and place them in the network that needs reaching. - Multi-repo:
resources: repositoriespluscheckout: selfandcheckout: other. - Container jobs run steps inside a specified image, so the toolchain is consistent.
- Matrix and parallel jobs for multi-version testing and sharding.
Reusable pipeline elements
| Mechanism | Description |
|---|---|
| YAML templates (Azure Pipelines) | template: references include step, job, stage and variable templates, with parameters. Extends templates (extends: template: ...) enforce a required structure. Combined with an environment Required template check, they force every pipeline to use approved steps |
| Task groups | Classic pipelines only: bundle a set of tasks reused across classic definitions. Not available in YAML (use templates) |
| Variables | Defined in YAML, in the UI, or via templates. Secret variables are masked and not passed to forks or scripts automatically (map into env:) |
| Variable groups | Library items shared across pipelines, optionally linked to Azure Key Vault to fetch secrets at run time. Pipelines need permission to use the group. Reference with - group: name |
| Secure files | Library files (certificates, provisioning profiles) |
| Template expressions | ${{ if eq(parameters.env, 'prod') }}: conditional insertion, ${{ each x in parameters.list }} loops |
| GitHub reusable workflows / composite actions | As above |
| GitHub variables and secrets | Organisation, repository and environment levels. Environment-level overrides repository, which overrides organisation |
# templates/build-steps.yml
parameters:
- name: configuration
default: Release
steps:
- script: dotnet build -c ${{ parameters.configuration }}
# azure-pipelines.yml
steps:
- template: templates/build-steps.yml
parameters:
configuration: Debug
Templates can live in another repository (template: file.yml@repoAlias) so a platform team shares them.
Checks and approvals with environments
An Environment is a named target for deployments (Kubernetes namespace, VM set, or just a logical stage). Environments give deployment history, traceability to commits and work items, and checks.
Checks (set on the environment, resource or service connection, secure file or variable group):
- Approvals (named users or groups, one or all must approve, timeout, instructions).
- Branch control (only allow from matching branches, such as
refs/heads/main). - Business hours.
- Invoke Azure Function / REST API, Query Azure Monitor alerts.
- Required template: the pipeline must extend a given template.
- Exclusive lock: one run at a time on the resource, others queue (or only the latest proceeds with
lockBehavior: sequentialorrunLatestin the pipeline). - Evaluate artifact (check image policy).
A check on the service connection or agent pool runs before the job even starts using it. Checks run in parallel, with the exception that approvals and branch control are evaluated in a defined order (static checks first, then dynamic ones). Environment approvals are for the deployment job, and a Manual Validation task (server job) is a different, in-pipeline pause.
GitHub environments: required reviewers (up to 6), wait timer, deployment branches and tags, environment secrets and variables, custom deployment protection rules. Environment secrets are only released to the job after the approvals.
Deployments
Deployment strategies
| Strategy | How | Risk and rollback | Best for |
|---|---|---|---|
| Recreate / big bang | Stop old, start new | Downtime, rollback = redeploy | Dev, tolerant systems |
| Rolling | Update instances in batches (maxParallel) |
Mixed versions during rollout, slow rollback | Stateless VM sets and Kubernetes |
| Blue-green | Two identical environments. Deploy to idle (green), test, switch traffic (swap slots, DNS or load balancer) | Instant rollback by switching back, costs double | Zero-downtime releases |
| Canary | Send a small percentage of traffic (5%, 25%…) to the new version, watch metrics, then widen | Small blast radius | Risky changes with good telemetry |
| Ring-based / progressive exposure | Release to rings in order: canary/internal -> early adopters -> broad -> everyone, with validation between | Staged, controlled | Large user bases, Microsoft’s own model |
| Feature flags | Deploy code dark, turn the feature on per user or percentage at runtime | Switch off without redeploying | Decouple deploy from release |
| A/B testing | Different users get different variants to compare a business metric | Experiment, not a safety net | Product experimentation |
Key differences: canary is about safety (lower risk), A/B is about learning (compare variants), blue-green is about switching all traffic at once with an instant way back, rings are staged audiences. Deployment decoupled from release is what feature flags give.
In YAML, deployment: jobs support strategy: runOnce, rolling (with maxParallel) and canary (with increments: [10, 20]) lifecycle hooks.
Minimising downtime
- Load balancer (Azure Load Balancer, Application Gateway, Front Door, Traffic Manager) drains and routes traffic. Take an instance out of rotation, update it, health-check, return it.
- Rolling deployments on VM scale sets, AKS (
RollingUpdatewithmaxUnavailableandmaxSurge) and Azure App Service. - Deployment slots (App Service Standard tier and above, Functions Premium/Dedicated): deploy to a staging slot, warm it up, run smoke tests, then swap. The swap redirects traffic without restart and can be swapped back for rollback. Slot-sticky settings (marked “deployment slot setting”) stay with the slot, so connection strings for staging don’t move to production. Swap with preview applies production settings to the staging slot first so you can test. Auto swap swaps after a successful deployment. Testing in production routes a percentage of traffic to a slot (canary).
- Health probes and readiness/liveness checks avoid sending traffic to cold instances.
- Database changes must be backward compatible (see below).
Dependency ordering
Deployments often depend on each other (database before app, shared service before consumers). Design the order:
- Use stages with
dependsOnand conditions. - Use pipeline resources to trigger a downstream pipeline after an upstream one completes.
- Add checks (Invoke REST API, Azure Monitor alerts) that confirm a dependency is healthy.
- Use exclusive lock and deployment ordering so older runs don’t overwrite newer ones.
- Version interfaces and use backward-compatible changes so consumer and provider can deploy independently. Deploy the provider first (expand), then consumers (migrate), then clean up (contract).
- For Bicep/ARM,
dependsOnand implicit references order resource creation within one deployment.
Hotfix path
A hotfix is an urgent production fix that shouldn’t wait for the normal queue.
- Branch from the production tag or release branch (not from
mainifmainhas unreleased work). - Make the minimal fix and open a PR with an expedited review (a smaller review quota is allowed by policy, never none).
- Run the same pipeline: build, fast tests and smoke tests. Don’t bypass the pipeline.
- Deploy through an expedited path (skip non-essential stages or approvals only through an agreed, pre-defined rule).
- Merge the fix back to
main(and to open release branches) so the bug doesn’t return. Cherry-pick if the branches have diverged. - Tag the hotfix release and update release notes.
Resiliency strategy for deployment
- Automatic rollback triggered by failed health checks or alerts (a post-deployment gate querying Azure Monitor, then redeploy the last known good artifact or swap back the slot).
- Immutable artifacts: promote the same package through environments rather than rebuilding.
- Retries on transient steps (
retryCountOnTaskFailure), timeouts, and idempotent scripts that can re-run. - Redundancy: deploy to multiple regions or availability zones, ordered one region at a time (
Traffic Manager/Front Doorfor failover), so a bad release doesn’t hit every region. - Canary and ring deployments limit blast radius, and feature flags disable a bad feature.
- Backups and restore points before data changes. Pipeline definitions in source, and a tested disaster-recovery pipeline.
Feature flags with Azure App Configuration
Azure App Configuration stores settings and feature flags. Feature Manager (Microsoft.FeatureManagement libraries) evaluates flags in code.
- A feature flag has a name, enabled state and optional filters: Percentage (a share of users), Time window (start and end), Targeting (specific users, groups and rollout percentage by group), or custom filters.
- Code:
if (await featureManager.IsEnabledAsync("NewCheckout")) { ... }, or[FeatureGate("NewCheckout")]on controllers, or<feature name="NewCheckout">in Razor. - Dynamic refresh: register a sentinel key so the app reloads configuration when the sentinel changes, without restart.
- Access with managed identity (App Configuration Data Reader role), and use Key Vault references for secrets.
- Variants and telemetry in newer releases support A/B experiments.
- Flags are config, so change them without a deployment. Remove stale flags to limit technical debt.
Application deployment: containers, binaries and scripts
| Style | What gets deployed | Typical tasks |
|---|---|---|
| Containers | An image built with Docker, pushed to Azure Container Registry (ACR) or ghcr.io, and run on AKS, Container Apps, App Service or Container Instances | Docker@2 (buildAndPush), KubernetesManifest@1, HelmDeploy@1, AzureContainerApps@1, azure/aks-set-context, azure/k8s-deploy, azure/webapps-deploy |
| Binaries/packages | A zip, WebDeploy package, JAR, nupkg | AzureWebApp@1, AzureFunctionApp@2, azure/webapps-deploy, az webapp deploy |
| Scripts | Bash, PowerShell, az CLI, SSH to VMs, Custom Script Extension |
AzureCLI@2, AzurePowerShell@5, SSH@0, PowerShell@2, azure/CLI |
For container workflows, sign in to the registry with a service connection or OIDC (never the admin account). Build with ACR Tasks (az acr build) if you don’t want a Docker daemon. Scan images before deploy (see the security notes). For Kubernetes: Helm charts or Kustomize parametrise manifests, and GitOps (Flux or Argo CD) pulls from Git instead of the pipeline pushing.
Database deployment tasks
- Treat the schema as code in source control. Approaches: state-based (SQL project/dacpac,
SqlAzureDacpacDeployment@1, SSDT) where a tool diffs desired state against the target, or migration-based (EF Core migrations, Flyway, Liquibase, Redgate) with ordered scripts. - Run after infrastructure and before or alongside the app. Use the expand and contract pattern so old and new app versions both work with the schema during rollout.
- Take a backup or point-in-time restore point before destructive changes. Use
SqlAzureDacpacDeployment@1,AzureCLI@2withsqlcmd, orazure/sql-actionin GitHub. - Add a manual approval or gate before the production schema change, and test migrations on a copy of production data.
- Avoid data loss: configure the dacpac
BlockOnPossibleDataLoss. - The deploy identity should be a managed identity or service principal granted
db_owneronly as needed.
Infrastructure as code
Configuration management technologies
| Tool | Style | Notes |
|---|---|---|
| ARM templates | Declarative JSON | Native to Azure, verbose |
| Bicep | Declarative DSL that compiles to ARM | Azure-only, concise, day-one support for new resource types, no state file, first-class in the exam |
| Terraform | Declarative HCL, multi-cloud | Keeps a state file (store in Azure Storage with locking), plan and apply |
| Azure Resource Manager (deployment stacks) | Manage a set of resources as a unit, with deny settings and lifecycle control | Deployment stacks protect and clean up resources |
| Ansible | Agentless, YAML playbooks, push | Configure OS and apps |
| Chef, Puppet | Agent-based, pull, DSL | Configure servers, state enforced |
| Azure Automation State Configuration (DSC) | PowerShell DSC pull | Being retired in favour of Azure Machine Configuration (the Automation service’s DSC for Azure/Arc VMs is deprecated, so new designs should use Azure Machine Configuration) |
| Azure Machine Configuration | Audit and configure settings inside guest OS (Windows/Linux), via Azure Policy, works on Arc-enabled servers | Replaces Guest Configuration and Automation State Configuration |
| Cloud-init, Custom Script Extension, VM Applications | First-boot or extension-based configuration | Simple |
Declarative says what the end state is and the engine figures out how. Imperative says how step by step (scripts). IaC wants declarative and idempotent tools: running twice gives the same result.
Immutable infrastructure: replace servers rather than patch in place (images from Packer or Image Builder). Mutable: change existing servers, needing DSC/Ansible/Machine Configuration to stop drift. Configuration drift is reality diverging from the definition.
IaC strategy
- Source control all templates; protect
mainwith PR review and checks. - Automate testing: lint (
bicep build,bicep lint,tflint), validate (az deployment group validate), what-if (az deployment group what-if, a PR comment of changes;terraform plan), policy-as-code tests (checkov, PSRule for Azure, Azure Policy), and an ephemeral integration test deployment. - Deployment through a pipeline with environments and approvals. Use parameter files (
.bicepparam) per environment, modules (published to a Bicep registry in ACR or template specs) and deployment scopes (resource group, subscription, management group, tenant). - Secrets from Key Vault references, never in parameter files. Deploy with OIDC federated service principals.
- Complete vs incremental mode in ARM: incremental (default) leaves resources not in the template; complete deletes them. Use deployment stacks or complete mode carefully.
- Run what-if before production, and re-run deployment on a schedule to correct drift.
param location string = resourceGroup().location
param storageName string
resource sa 'Microsoft.Storage/storageAccounts@2023-05-01' = {
name: storageName
location: location
sku: { name: 'Standard_LRS' }
kind: 'StorageV2'
}
- task: AzureCLI@2
inputs:
azureSubscription: svc-conn
scriptType: bash
scriptLocation: inlineScript
inlineScript: |
az deployment group what-if -g rg-app -f main.bicep -p main.bicepparam
az deployment group create -g rg-app -f main.bicep -p main.bicepparam
Desired state configuration
- Azure Resource Manager / Bicep define Azure resources and reapply to converge.
- Azure Policy enforces and audits resource properties (
deny,audit,modify,deployIfNotExists). - Azure Machine Configuration assesses and (in enforce mode) remediates settings inside machines, such as installed software, registry or file settings, using DSC (Windows) or Chef InSpec (Linux, earlier) packages assigned by policy, covering Azure VMs and Azure Arc servers.
- Azure Automation State Configuration (DSC pull server) is the legacy service for the same job; know it exists on the exam, but prefer Machine Configuration for new work.
- Bicep/ARM don’t configure the OS. Combine them with Machine Configuration, Custom Script Extension, VM Applications or Ansible for the guest.
Azure Deployment Environments
Azure Deployment Environments (ADE) lets developers self-service create standardised environments on demand, from catalogs of IaC templates (Bicep, ARM, Terraform, in a GitHub or Azure Repos repo) set up by platform engineers.
- Pieces: Dev centre (holds catalogs and environment types), Project (developer-facing, maps to team and subscription), Environment types (dev, test, sandbox, mapped to subscriptions with permissions and managed identity), Catalog (templates in Git), and environment definitions (the template plus
environment.yamlmanifest). - Developers create environments from the portal, CLI or developer portal; platform teams control cost and policy centrally through environment types, and can set expiry dates and delete automatically.
- It works with pipelines: create a deployment environment per PR, run tests, and delete it.
- It is not a hosting service; it provisions the resources you define.
Maintaining pipelines
Pipeline health
- Azure Pipelines Analytics > Pipeline reports: pass rate, failure trend, duration, failing tasks, test pass rate and flaky tests. The Tests tab marks a test as flaky when it passes and fails on the same code; you can exclude flaky tests from the pass-rate and the failure status.
- GitHub Actions: workflow run history, usage metrics, run duration, and status badge. Create alerts for failures with notifications or a workflow that posts to Teams.
- Track the DORA metrics, queue wait time and failure causes (infrastructure vs code vs tests).
- Treat flaky tests as defects: quarantine them, fix root causes (timing, shared state, external services), re-run only failed jobs.
Optimise for cost, time, performance and reliability
| Goal | Techniques |
|---|---|
| Time | Cache dependencies (Cache@2, actions/cache), run jobs and test slices in parallel, shallow clones (fetchDepth), path filters to skip unaffected builds, incremental builds, smaller images, pre-baked agent images, avoid repeating builds (build once, deploy many) |
| Cost | Right-size agents, scale set agents that scale to zero, spot VMs, cancel superseded runs (autoCancel, cancel-in-progress), shorter artifact retention, skip docs-only changes, run expensive tests on schedule or on main only |
| Performance | Faster agent SKUs or larger runners for heavy builds, local SSD, container jobs with cached layers, parallel multi-agent tests |
| Reliability | Retry transient failures, pinned versions and lock files, ephemeral agents, health checks, timeouts, deterministic builds, fix flaky tests |
Concurrency
- Azure DevOps parallel jobs limit how many jobs run at once. Extra jobs queue. Buy more, or move to self-hosted agents (each needs a parallel job licence beyond the free one) and Managed DevOps Pools. Check Project settings > Parallel jobs and Pipelines > Analytics > queue time. Set
maxParallelin strategies to avoid hogging capacity, and exclusive locks to serialise deployments. UselockBehavior. - GitHub: job concurrency limits depend on plan (for example the free plan allows 20 concurrent jobs, Team 60, Enterprise 500 for standard runners); macOS jobs have lower limits. Use
concurrency:groups to avoid overlaps. Larger runners have their own limits and autoscaling.
Retention strategy
- Azure Pipelines: retention policies at project settings (days to keep runs, artifacts, pull request runs, and so on), per-pipeline retention leases to keep runs that were released to production (for example a run is retained by a lease while its release is live), and manual retain indefinitely. Pipeline artifacts expire with the run, so retain the ones you deploy or promote.
- GitHub: artifact and log retention (1 to 90 days, default 90; set per repository, organisation or
retention-days:on upload). Packages and release assets stay until deleted. Cache entries are evicted after 7 days unused, with a 10 GB per repository default cap. - Dependencies: Azure Artifacts feed retention policies and keeping upstream copies in the feed keep a repeatable build possible. Keep a SBOM for shipped versions.
- Retain what you need for audits (compliance period) but delete the rest to save storage cost.
Migrate classic to YAML
Classic pipelines are designed in the UI (build definitions and release definitions with stages, tasks, gates). To migrate:
- In a classic build pipeline, use Export to YAML (the “View YAML” button) to get a starting file; the Azure Pipelines migration tool and
Pipelines > ... > Export to YAMLdo the same. - Check in
azure-pipelines.yml. Convert the release pipeline into stages withdeploymentjobs mapping to environments. - Replace classic task groups with templates, and variable groups stay (use
- group:). - Convert triggers (CI, PR, schedule) into
trigger,pr/branch policy,schedules. - Replace release approvals and gates with environment checks.
- Move artifact sources to pipeline resources.
- Test in parallel with the old pipeline, then disable the classic one.
Why migrate: YAML is versioned with the code, reviewable by PR, reusable through templates, and the only path for new features (classic release pipelines are in maintenance mode and some features like multi-stage and templates exist only in YAML). A gap: classic releases offered manual release creation with per-release variable overrides and deployment groups, which map to runtime parameters and environment VM resources.
Exam checklist
- You can pick Azure Artifacts (feeds, views, upstream) vs GitHub Packages, and explain feed views promotion and
@Local,@Prerelease,@Release. - You know SemVer, CalVer and how Azure Pipelines run numbers and GitVersion produce versions.
- You can design the test stack, publish results and coverage, and place gates, with Azure Load Testing for load.
- You can compare Azure Pipelines and GitHub Actions, and know the hosted, self-hosted, scale set, Managed DevOps Pool and ARC options and what drives the choice (private network, cost, maintenance).
- You can write and read YAML: stages, jobs, steps,
dependsOn, conditions, deployment jobs, templates, parameters vs variables, output variables, matrix. - You know variable groups can link to Key Vault, task groups are classic only, and environments give approvals, branch control, exclusive lock and required template checks.
- You can contrast blue-green, canary, rolling, ring and A/B, plus feature flags, and name slot swap and sticky settings for zero downtime.
- You know App Configuration feature filters (percentage, time window, targeting) and the sentinel key.
- You can describe hotfix flow, database deployment with expand and contract, and rollback designs.
- You can choose Bicep vs ARM vs Terraform, what-if, Azure Machine Configuration vs Automation State Configuration, and describe Azure Deployment Environments pieces.
- You know how to monitor pipeline health, optimise caching and concurrency, set retention, and migrate classic pipelines to YAML.