Recommend an Automated Deployment Solution for Applications — Lesson
AZ-305 › Unit 4: Design infrastructure solutions › Design an application architecture › Recommend an automated deployment solution for applications
Recommend an Automated Deployment Solution for Applications — Lesson
A platform team supports 40 services across 3 business units. Every team deploys by clicking "Publish" in Visual Studio and emailing the rest of the company. Tuesday afternoons a service goes down for 35 minutes because two engineers redeployed the same module within a minute of each other and the second wiped the first's hotfix. The architect's response is one decision: every production deployment must come from Azure Pipelines running a versioned Bicep file in source control, with blue-green slots and an automated rollback gate. Six months later, deployment frequency is higher, mean-time-to-recovery is 80% lower, and "deploy collisions" are no longer a category of incident. This lesson is about choosing the right automated-deployment stack — CI/CD pipelines plus IaC plus deployment strategy — so shipping is the team's superpower, not their bottleneck. The same architectural decisions matter on day 1 of a new project and on day of a mature platform: the wrong CI tool, the wrong IaC language, or the wrong deployment strategy compounds friction for years; the right ones compound velocity for years.
We will work through Azure's automated-deployment story the way the AZ-305 exam expects you to: choosing between Azure Pipelines and GitHub Actions, comparing IaC options (Bicep, ARM Templates, Terraform, Pulumi), and configuring deployment strategies (slots, blue-green, canary, rolling). Reference: the AZ-305 exam study guide, particularly Chapter 4 Skill 4.2 on automated deployment.
Why This Matters
Automated deployment is the practice that separates teams who ship daily from teams who ship quarterly — and the difference compounds: high-frequency teams catch regressions in hours, low-frequency teams catch them in weeks. The AZ-305 exam tests this LO because architects make foundational decisions about CI/CD tooling and IaC language that lock teams in for years. Pick Azure Pipelines when the org already lives in Azure DevOps; pick GitHub Actions when the source of truth is GitHub; mixing them is friction. Pick Bicep for Azure-native simplicity; pick Terraform for multi-cloud or for a team already fluent in HCL.
The career payoff is concrete: every "modernise our deployment" initiative, every "we need to ship faster" review, every regulated-deployment audit, every post-incident retro that asks "why did our rollback take twenty minutes?" touches this LO. If you can match a team to the right pipeline / IaC / deployment-strategy combination and configure environments, approvals, and rollback gates correctly, you will pass this slice of the exam and design CI/CD like a senior architect — turning shipping from a fragile manual ritual into an everyday production-grade automated process that everyone trusts.
Prerequisites
Before working through this lesson, make sure you can answer each prompt below in one or two sentences.
- CI/CD basics. Can you describe the difference between continuous integration and continuous deployment? — Self-check: which one runs every commit?
- IaC concept. Are you familiar with declarative vs imperative infrastructure provisioning? — Self-check: which model does Terraform use?
- Deployment strategies. Do you know blue-green, canary, rolling, recreate? — Self-check: which one runs two full environments side by side?
- App Service deployment slots. Have you used them? — Self-check: what does the "swap" operation do?
- Service principals / managed identity in pipelines. Are you familiar with how a pipeline authenticates to Azure? — Self-check: what is OpenID Connect federation for GitHub Actions?
If any of these feels shaky, review the CI/CD and IaC intro modules in Unit 4 of the AZ-305 guide.
Learning Objectives
By the end of this lesson, you will be able to:
- Analyse a team's deployment requirements (source control system, target environment, governance, frequency) and translate them into a CI/CD stack.
- Evaluate
Azure PipelinesvsGitHub ActionsandBicepvsARM TemplatesvsTerraformvsPulumifor a workload. - Design a multi-stage pipeline with PR gates, approvals, environments, and OIDC-based identity.
- Recommend a deployment strategy (slots, blue-green, canary, rolling) based on workload risk and roll-back semantics.
- Recognise anti-patterns — manual portal changes, service-principal secrets in pipeline variables, no environment promotion, no rollback gate — and rewrite them.
- Configure PR validation, what-if previews, and post-deployment health gates that block bad releases.
Building Blocks
Read this section as a glossary. Each term: analogy, formal definition, why it matters.
CI (continuous integration) — The practice of merging code changes frequently with automated build and test on every change. Like a hot-air balloon's regular lift-checks. Formally, automated pipelines that run on every commit / PR to validate the build and test suite. It matters because CI is the gate that keeps the main branch always-deployable.
CD (continuous delivery / deployment) — The practice of automating the path from a green main-branch build to production. Formally, two flavours: delivery (automation up to a manual approval) or deployment (automated all the way to production). It matters because CD is what turns "we can ship" into "we do ship".
Azure Pipelines — Microsoft's CI/CD service in Azure DevOps. Like a CI server with deep Azure integration. Formally, Microsoft.DevOps/Azure DevOps' YAML pipelines and classic UI pipelines that run on Microsoft-hosted or self-hosted agents. It matters because Pipelines is the canonical choice when the org's source control is Azure DevOps Repos.
GitHub Actions — GitHub's CI/CD product. Formally, YAML-defined workflows triggered by repository events, running on GitHub-hosted or self-hosted runners. It matters because GitHub Actions is the canonical choice when source control is GitHub — and GitHub has overtaken Azure DevOps Repos as the modern default.
Bicep — Microsoft's declarative IaC language for Azure. Like a higher-level dialect of ARM templates. Formally, a transpiled-to-ARM language with stronger types, modules, and resource shorthand. It matters because Bicep is the Microsoft-recommended IaC for Azure-only workloads — clean syntax, full Azure provider coverage, no state file to manage.
Terraform — HashiCorp's multi-cloud IaC tool. Formally, HCL declarative language plus a state file plus providers per cloud. It matters because Terraform is the right answer for multi-cloud workloads or teams already fluent in HCL. AzureRM and AzAPI providers cover Azure.
Deployment slot — An App Service feature that hosts two (or more) live versions and lets you swap them. Like swapping a printer cartridge while the printer keeps printing. Formally, slots share an App Service plan, each with its own configuration and code, with a Swap operation that flips routing. It matters because slots enable blue-green for App Service in one click.
Blue-green — A deployment strategy that runs two full environments side-by-side and switches all traffic from one to the other. Formally, "blue" = current, "green" = new; flip the load balancer / DNS / slot to switch. It matters because it gives instant rollback by flipping back.
Canary — A deployment strategy that exposes a small percentage of traffic to the new version before rolling out. Formally, gradual traffic shift () with health monitoring at each step. It matters for risky changes — you can detect regressions on a small blast radius.
OIDC federation — A way to let an external pipeline (GitHub Actions, etc.) authenticate to Azure without storing secrets. Formally, federated credentials between the pipeline's OIDC issuer and an Entra ID app registration. It matters because it eliminates the long-lived service-principal secret in CI/CD — a frequent source of leaked credentials.
Deep Dive
1. Azure Pipelines vs GitHub Actions — pick by source control
| Aspect | Azure Pipelines | GitHub Actions |
|---|---|---|
| Source-control alignment | Best with Azure DevOps Repos | Best with GitHub repos |
| Pipeline definition | YAML or classic | YAML |
| Reusable units | Templates, task groups | Composite / reusable workflows |
| Marketplace | Azure DevOps Marketplace tasks | GitHub Marketplace Actions |
| Self-hosted runners | Yes (Azure Pipelines Agent) | Yes (GitHub Actions Runner) |
| OIDC to Azure | Yes (workload identity federation) | Yes (OIDC federation, no secret) |
| Best for | Enterprise teams on Azure DevOps | GitHub-centric teams (the modern default) |
[!TIP] If a team's source code is in GitHub, default to GitHub Actions. If it's in Azure DevOps Repos, default to Azure Pipelines. Mixing the source-control product and the CI product is an unnecessary integration cost.
2. IaC language choice — Bicep vs ARM vs Terraform vs Pulumi
| Language | Cloud(s) | State | Strengths |
|---|---|---|---|
Bicep | Azure only | None (ARM does it) | Concise, full Azure coverage, Microsoft-supported, no state file |
ARM Templates | Azure only | None | Foundational format (Bicep transpiles to ARM); verbose JSON |
Terraform | Multi-cloud | Yes (Terraform state) | Industry standard for multi-cloud; mature provider ecosystem |
Pulumi | Multi-cloud | Yes (Pulumi backend) | Imperative SDK in C#/TS/Python/Go; programmatic abstractions |
Microsoft's recommendation for Azure-only workloads is Bicep. For multi-cloud workloads or teams already fluent in HCL, Terraform is the right answer. ARM Templates are still in use for legacy templates but new templates should be authored in Bicep.
@description('Web app with deployment slots and managed identity')
param location string = resourceGroup().location
param appName string
resource plan 'Microsoft.Web/serverfarms@2023-12-01' = {
name: '${appName}-plan'
location: location
sku: { name: 'P1v3', tier: 'PremiumV3' }
properties: { reserved: true }
}
resource app 'Microsoft.Web/sites@2023-12-01' = {
name: appName
location: location
kind: 'app,linux'
properties: { serverFarmId: plan.id, httpsOnly: true }
identity: { type: 'SystemAssigned' }
}
resource stagingSlot 'Microsoft.Web/sites/slots@2023-12-01' = {
parent: app
name: 'staging'
location: location
properties: { serverFarmId: plan.id, httpsOnly: true }
identity: { type: 'SystemAssigned' }
}[!NOTE] Bicep does not require a state file. The Azure Resource Manager is the state —
what-ifmode lets you preview changes before applying.
3. Deployment strategies — slots, blue-green, canary, rolling, recreate
| Strategy | Pattern | Best for | Rollback |
|---|---|---|---|
| Recreate | Stop old, start new | Dev / non-critical | Re-deploy old |
| Rolling | Replace instances one at a time | Many-instance fleets with capacity headroom | Re-deploy old; slow |
| Blue-green | Two environments, switch all traffic | App Service slots, AKS Service swap | Flip back; instant |
| Canary | Gradual traffic shift to new | Customer-facing high-risk changes | Shift back; partial blast radius already |
| A/B test | Like canary but with deliberate audience targeting | Product experiments | Toggle feature flag |
[!TIP] App Service deployment slots are the easiest blue-green primitive in Azure — swap-with-preview gives you a final sanity check before flipping. Combine with a health-gate task in the pipeline that polls a
/healthendpoint and aborts if it returns non-200 for seconds.
4. Pipeline structure — stages, environments, approvals, OIDC
A production pipeline has at least four stages: build, test, deploy-to-staging, deploy-to-prod. Each stage runs against an "environment" that may require an approver before proceeding.
# Azure Pipelines: multi-stage pipeline with approvals
trigger:
branches: { include: [main] }
stages:
- stage: Build
jobs:
- job: BuildAndTest
pool: { vmImage: ubuntu-latest }
steps:
- task: UseDotNet@2
inputs: { version: '8.x' }
- script: dotnet build && dotnet test
- stage: DeployStaging
dependsOn: Build
jobs:
- deployment: Deploy
environment: staging
strategy:
runOnce:
deploy:
steps:
- task: AzureCLI@2
inputs:
azureSubscription: 'staging-oidc' # service connection with OIDC federation
scriptType: bash
scriptLocation: inlineScript
inlineScript: |
az deployment group what-if --resource-group rg-app --template-file main.bicep
az deployment group create --resource-group rg-app --template-file main.bicep
- stage: DeployProduction
dependsOn: DeployStaging
jobs:
- deployment: Deploy
environment: production # has manual approval check
strategy:
runOnce:
deploy:
steps:
- task: AzureWebApp@1
inputs:
azureSubscription: 'prod-oidc'
appType: webApp
appName: $(appName)
deployToSlotOrASE: true
slotName: staging
- task: AzureAppServiceManage@0
inputs:
azureSubscription: 'prod-oidc'
action: 'Swap Slots'
webAppName: $(appName)
sourceSlot: staging[!IMPORTANT] Use
what-if(Bicep) orterraform planto preview changes before applying. The preview output should be visible in the pipeline log so reviewers can see exactly what will change.
5. OIDC federation — pipelines without secrets
Both Azure Pipelines and GitHub Actions support OIDC federation. The flow:
- Create an Entra ID app registration with a federated credential bound to the pipeline (audience:
api://AzureADTokenExchange, subject: a specific repo + branch or environment). - Grant the app's principal the necessary Azure RBAC roles.
- In the pipeline, request an OIDC token; Azure CLI exchanges it for an Azure access token.
# GitHub Actions: OIDC to Azure
jobs:
deploy:
permissions:
id-token: write
contents: read
runs-on: ubuntu-latest
steps:
- uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- run: az deployment group create --resource-group rg-app --template-file main.bicep[!WARNING] Do not store service-principal client secrets in pipeline variables. Even encrypted variables can leak in scripts that echo them. OIDC federation eliminates the secret entirely.
6. Environments, secrets, and self-hosted runners
A production-grade pipeline has at least three environments mapped to Azure: dev, staging, prod. Each environment has its own RBAC, its own approval policy, and (sometimes) its own self-hosted runner pool.
| Environment | Approval | Identity scope | Network |
|---|---|---|---|
dev | None | Dev subscription | Public runners |
staging | None or single reviewer | Staging subscription | Public or self-hosted |
prod | reviewers, change ticket required | Production subscription | Self-hosted in restricted VNet |
Self-hosted runners matter when the deployment target is private (e.g., AKS API server behind Private Link, or App Service with public access disabled). Microsoft-hosted runners cannot reach private endpoints; self-hosted runners deployed inside the customer VNet can.
[!TIP] For tightly-regulated workloads, use scaled-set runners (Azure Pipelines) or ARC (Actions Runner Controller) in AKS for GitHub Actions. Both provide ephemeral runners that spin up per job and destroy themselves on completion — reducing the surface area of long-lived state.
7. Observability — knowing if the deployment worked
// Find App Service deployments in the last 24 hours
AzureActivity
| where TimeGenerated > ago(24h)
| where OperationNameValue == "Microsoft.Web/sites/slotsswap/action"
| project TimeGenerated, ResourceId, Caller, ActivityStatusValue, Properties
| order by TimeGenerated descPair with Application Insights for app-side metrics — a deployment that "succeeds" in the pipeline but raises the rate by should trigger automatic rollback via an alert that calls the deployment service to swap slots back.
8. PR validation and what-if previews
A core practice is PR validation: every pull request runs the pipeline up to the deploy step against a temporary or shared review environment. This catches breaking changes before merge. The exam tests the difference between "what-if" (Bicep) and "plan" (Terraform):
| Tool | Preview command | What it shows |
|---|---|---|
Bicep | az deployment group what-if | Diff of proposed vs current ARM state |
Terraform | terraform plan | Diff of proposed vs tfstate |
The preview belongs in the PR conversation: post the diff as a PR comment so reviewers can see exactly what will change. Modern Azure Pipelines and GitHub Actions both support this pattern out of the box.
[!IMPORTANT] A PR that changes infrastructure should require both code review and an infra-engineer's sign-off. Use branch protection rules + CODEOWNERS to enforce this automatically.
Worked Examples
Easy — pick the CI/CD stack
Problem. A team's code lives in GitHub. They deploy to Azure App Service. They want minimal friction. Recommend CI/CD and IaC tools.
Solution. GitHub Actions with Bicep. GitHub Actions matches source control; Bicep matches Azure-only targets. Configure OIDC federation so workflows authenticate to Azure without secrets. Use App Service deployment slots for blue-green.
Medium — multi-stage pipeline with approvals
Problem. A regulated workload deploys to staging on every merge, but production deployments must wait for a security manager's approval. Recommend a structure.
Solution. Azure Pipelines (or GitHub Actions) with two environments: staging (auto-deploys on merge) and production (gated by a manual approval in environment settings). Production deployment uses slot-swap. Health-gate task runs after deployment and rolls back if the smoke test fails. All deployments authenticated via OIDC.
Hard — canary for a high-traffic API
Problem. A high-traffic public API needs to deploy a risky change. The team wants 5% of traffic to hit the new version first; if error rates stay under 0.5% over 30 minutes, increase to 50%, then 100%. Recommend.
Solution. Use traffic-split deployment via Azure Front Door weighted routing or Container Apps revision traffic split. The pipeline deploys the new revision with 5% initial weight; an automated post-deployment task monitors the rate via Application Insights for 30 minutes; if healthy, the pipeline shifts weight to 50% and reruns the check; if healthy again, shifts to 100%. Failure at any stage triggers an automatic shift back to 0%.
# Container Apps revision traffic split
revisions:
- revisionName: web-rev-v1.5
weight: 100
- revisionName: web-rev-v1.6
weight: 0 # bumped to 5, then 50, then 100 by the pipelineVisual Explanations
Figure 1 — CI/CD decision flow
Figure 2 — Blue-green via App Service slots
Figure 3 — Quick chooser
| Scenario | Stack |
|---|---|
| GitHub + Azure-only | GitHub Actions + Bicep |
| Azure DevOps Repos + Azure-only | Azure Pipelines + Bicep |
| Multi-cloud | Either CI + Terraform |
| Highly-imperative provisioning | Pulumi |
| Tiny team, just deploy | App Service local Git deploy (dev only) |
| Risky change to public API | Canary via Container Apps revisions or AFD weights |
| Stateful workload, downtime tolerated | Recreate |
| Need instant rollback | Blue-green slot swap |
Common Mistakes
❌ Myth: "Manual deployments are fine for small teams." ✅ Reality: Manual deployments breed inconsistency: someone forgets a step, two engineers deploy simultaneously, the runbook drifts from reality. Even one-engineer projects benefit from a pipeline. Why it's tricky: "Small team" sounds like a reason to skip automation; the bigger the team, the worse the manual costs grow.
❌ Myth: "Store the service-principal secret in pipeline variables — it's encrypted." ✅ Reality: Encrypted variables still get printed by sloppy scripts and decoded by anyone with pipeline-edit rights. OIDC federation removes the secret entirely. Why it's tricky: Secrets-in-variables works, so teams stop there; the leak is invisible until it happens.
❌ Myth: "Terraform is better than Bicep because it's industry-standard." ✅ Reality: For Azure-only workloads, Bicep is supported by Microsoft, has no state to manage, and uses ARM as the source of truth. For multi-cloud or HCL-fluent teams, Terraform wins. The right answer depends on the workload, not on industry fashion. Why it's tricky: Resume-driven decisions favour Terraform; workload fit favours Bicep for Azure-only.
❌ Myth: "App Service slot swap is risk-free." ✅ Reality: Slot swap warms the new instance before flipping traffic, but warm-up failures can still cause a brief error spike. Use
Auto Swapwith a health-check URL and post-swap monitoring to catch regressions. Why it's tricky: The swap looks atomic; the underlying warm-up has measurable risk.
Practice Exercises
🟢 Exercise 1. A team's code is in GitHub. The deployment target is Azure. Recommend a CI tool.
▶💡 Hint
Match the CI to the source control.
▶✅ Solution
GitHub Actions. Workflows live alongside code, OIDC federation to Azure removes secrets, and the GitHub Marketplace has first-class Azure deployment actions. Azure Pipelines would also work but is the right choice when source is in Azure DevOps Repos.
🟡 Exercise 2. A workload spans Azure, AWS, and on-prem. Recommend an IaC tool.
▶💡 Hint
Multi-cloud.
▶✅ Solution
Terraform. Bicep is Azure-only. Pulumi is multi-cloud but requires a programmatic SDK rather than declarative HCL. Terraform's mature multi-provider ecosystem makes it the standard choice; manage state in a remote backend (Terraform Cloud or azurerm backend in an Azure storage account).
🟡 Exercise 3. A team's CI/CD uses a long-lived service-principal secret stored in pipeline variables. Recommend a fix.
▶💡 Hint
OIDC federation.
▶✅ Solution
Configure OIDC federation between the pipeline's identity (GitHub Actions workflow or Azure DevOps service connection) and an Entra ID app registration. Update the pipeline to use the OIDC flow (azure/login@v2 for GitHub Actions). Remove the stored secret. Rotate the old secret as a final hygiene step.
🔴 Exercise 4. A pipeline deploys to production directly from main with no preview. A misconfigured Bicep parameter wipes a production storage account. Recommend procedural fixes.
▶💡 Hint
What-if + approvals + protected environments.
▶✅ Solution
(1) Add az deployment group what-if (or terraform plan) as a required pipeline step with output visible in the log. (2) Configure the production environment to require manual approval. (3) Add deny-delete locks on critical resources (Microsoft.Authorization/locks of kind CanNotDelete). (4) Configure post-deployment health gates that auto-rollback on regression.
🔴 Exercise 5. A high-traffic public API team wants 5% canary deployments. Currently they deploy via Bicep apply to App Service. Recommend.
▶💡 Hint
App Service deployment slots support weighted traffic.
▶✅ Solution
Use App Service traffic-routing percentage on the staging slot (5% of production traffic). After verifying health, increase to 50%, then swap. Alternatively, migrate to Container Apps for first-class revision traffic split, or front the app with Azure Front Door with weighted routing across two backend pools.
🟢 Exercise 6. True or false: Bicep requires a state file like Terraform.
▶💡 Hint
ARM is the state.
▶✅ Solution
False. Bicep transpiles to ARM templates; Azure Resource Manager itself tracks the deployed state. There is no separate tfstate-equivalent file to manage. This simplifies operations relative to Terraform but means you cannot easily import resources or manage state externally.
🟡 Exercise 7. Design a GitHub Actions workflow stub that uses OIDC and deploys a Bicep file to a staging environment.
▶💡 Hint
Need id-token: write permission and azure/login@v2.
▶✅ Solution
name: Deploy
on:
push: { branches: [main] }
permissions:
id-token: write
contents: read
jobs:
staging:
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/checkout@v4
- uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- run: az deployment group what-if -g rg-staging -f main.bicep
- run: az deployment group create -g rg-staging -f main.bicepSummary & Concept Map
- Match the CI tool to the source-control tool. GitHub GitHub Actions; Azure DevOps Repos Azure Pipelines.
- Default to Bicep for Azure-only IaC; Terraform for multi-cloud. Pulumi for imperative-SDK preference.
- Use OIDC federation. No service-principal secrets in pipeline variables.
- Deploy via stages with approvals. Build staging (approval) production.
- Pick the deployment strategy by risk. Recreate rolling blue-green canary, in order of risk-reduction.
- Always include a health gate. Auto-rollback on regression; manual rollback is too slow for tier-1 outages.