DevOps Lesson 107 of 137

Zero-Downtime Blue-Green Deployments on Azure: App Service Slots, Front Door, and Pipeline Automation

Blue-green deployment promises a release you can roll back in seconds instead of redeploying under pressure. On Azure this is achievable with native primitives — App Service deployment slots for the swap, Azure Front Door for a gradual edge cutover — but the gap between the demo and a safe production pipeline is where teams get burned. This guide builds the full flow: health-gated swaps, weighted traffic shifting, and a one-action rollback, all driven from CI/CD.

In a nutshell

Imagine a theatre with two identical stages side by side and one set of lights. Only the lit stage faces the audience; the other sits in the dark. When you want to premiere a new show, you build and rehearse it fully on the dark stage while the audience keeps watching the current show on the lit one. When the new show is ready, you flip the lights — the dark stage becomes the lit one in an instant, and the audience never sees a gap. If the new show flops, you flip the lights straight back. The old show is still standing, untouched, on what is now the dark stage.

That is blue-green deployment. “Blue” and “green” are just names for the two stages. One is live (serving all your real users); the other is idle (where you deploy and test the new version). You never edit the live one. You deploy to the idle one, warm it up, prove it works, then swap — an atomic flip that moves every user to the new version at once. If something breaks, you swap back in seconds, because the previous version is still sitting right there.

On Azure App Service, the two stages are deployment slots: the production slot (live) and a staging slot (idle candidate). The “flip the lights” moment is the swap. Because the swap only re-points the hostname — it does not redeploy anything — rollback is not a rebuild-under-pressure; it is just another swap. For high-traffic apps you can add Azure Front Door in front to dim the lights gradually (send 10% of the audience to the new stage first), and Traffic Manager to run the same trick across two regions.

Level: Intermediate · Time: ~27 min

Prerequisites: You should know what Azure App Service is and that a staging slot needs a Standard tier plan or higher. Comfort with the az CLI and a basic CI/CD pipeline helps. If blue-green is new to you, skim Secure zero-downtime deployments on App Service first, and Front Door + Traffic Manager global failover for the edge-routing half.

After this lesson you can:

Blue-green on App Service: deploy to green slot → warm up → swap → instant rollback

Read left to right: your pipeline deploys the new build to the idle green staging slot, App Service warms it and (optionally) previews production config on it, the swap flips green and blue atomically so users move all at once, and Front Door or Traffic Manager can ramp or steer that traffic — with rollback always one swap away.

1. Blue-green vs canary vs rolling: pick the strategy

These three terms get used interchangeably and they are not the same. The right choice depends on whether your app is stateful and how much blast radius you can tolerate.

Strategy How it works Rollback Best for
Rolling Replace instances in batches in place Roll forward (slow) Stateless apps where partial-version overlap is fine
Canary Route a small % to the new version, ramp on metrics Reduce % to zero High-traffic services with strong telemetry and SLOs
Blue-green Two full environments; cut all traffic at once after validation Flip back to the old environment Apps needing a clean version boundary and instant rollback

Blue-green’s defining property is two complete, parallel environments — only one serves production at a time. That clean boundary is exactly what makes it friendly to stateful apps: there is never a moment when v1 and v2 both own the same in-process session state, because the cutover is atomic.

On App Service, the two environments are the production slot (green, live) and a staging slot (blue, the candidate). The swap is the atomic cutover. Front Door sits in front and lets you turn that binary cutover into a gradual one when you want canary-style risk reduction at the edge — the best of both models.

The most common mistake is treating slot warm-up as optional. A swap without warm-up is a cold start in disguise: production instances start serving while still JIT-compiling and filling caches. Zero-downtime requires the candidate to be warm before traffic moves.

2. Deployment slots deep dive

A staging slot is a full, addressable copy of the app running on the same App Service Plan. You deploy to it, warm it, validate it, then swap. Slots require Standard tier or higher.

az webapp deployment slot create \
  --resource-group rg-app-prod \
  --name app-orders-prod \
  --slot staging

Slot settings: what travels during a swap

This is the single subtlety that breaks more blue-green setups than anything else. By default, app settings and connection strings follow the slot — they move with the code during a swap. That is correct for things that should promote with the release and catastrophic for environment-specific config (you do not want staging’s database connection string becoming production’s).

Mark environment-specific values as slot settings (a.k.a. “deployment slot setting” or “sticky”) so they stay pinned to the slot and do not travel:

az webapp config appsettings set \
  -g rg-app-prod -n app-orders-prod --slot staging \
  --slot-settings \
    ASPNETCORE_ENVIRONMENT=Staging \
    "SqlConnection=@Microsoft.KeyVault(SecretUri=https://kv-orders-prod.vault.azure.net/secrets/sql-conn/)"
Setting type Behavior on swap Use for
Regular app setting Travels with the code Feature flags, tuning that should promote with the release
Slot setting (sticky) Stays pinned to the slot Environment name, env-specific connection strings, slot-scoped keys

A useful discipline: connection strings should generally be slot settings, while feature flags and app-version metadata should generally travel. Audit slotSetting: true before every release.

Warm-up so the swap is genuinely zero-downtime

App Service can ping a path on every candidate instance and wait for healthy responses before completing the swap. This is the mechanism that turns a swap from a cold start into a true zero-downtime cutover.

az webapp config appsettings set \
  -g rg-app-prod -n app-orders-prod --slot staging \
  --slot-settings \
    WEBSITE_SWAP_WARMUP_PING_PATH=/health/ready \
    WEBSITE_SWAP_WARMUP_PING_STATUSES="200,202"

# Keep the slot from idling out before a swap
az webapp config set -g rg-app-prod -n app-orders-prod --slot staging --always-on true

WEBSITE_SWAP_WARMUP_PING_PATH and WEBSITE_SWAP_WARMUP_PING_STATUSES gate the swap on your readiness endpoint returning an acceptable status on each instance. The endpoint must check real dependencies — database reachable, Key Vault references resolved, cache primed — not return 200 unconditionally. A trivial health check defeats the entire purpose of warm-up gating.

3. The database and stateful-dependency problem

Blue-green’s atomic cutover does not exempt you from the hardest part: both slots talk to the same backing data. The staging slot is not a parallel database; it is a parallel application pointed at the same SQL, the same cache, the same queues. That has consequences.

Schema changes must be backward compatible across the swap window. During preview and immediately after a swap, both old and new code can hit the database simultaneously. The rule is expand/contract (a.k.a. parallel-change):

  1. Expand: deploy a schema change that is additive only — new nullable columns, new tables, new optional parameters. Old code ignores them; new code uses them.
  2. Migrate + swap: ship the new code that reads/writes the new shape. Both versions coexist safely because the old columns still exist.
  3. Contract: in a later release, once nothing runs the old code, drop the deprecated columns.

Never combine a destructive migration (drop column, rename, tighten a constraint) with the same release that depends on it. If you have to roll back, the old code will hit a schema it no longer understands and your “instant rollback” becomes an outage. Destructive changes are always a separate, later release.

For other stateful dependencies:

4. Health-gated auto-swap with swap-with-preview

The robust production pattern is swap with preview (a two-phase swap). Phase 1 applies the target (production) configuration to the staging slot and restarts it under production config — without moving any traffic. You validate the slot now running production config, then complete the swap.

# Phase 1: apply production config to staging, no traffic moved yet
az webapp deployment slot swap \
  -g rg-app-prod -n app-orders-prod \
  --slot staging --target-slot production --action preview

# ... run smoke tests against the staging slot, now running prod config ...

# Phase 2: complete the swap (traffic moves atomically)
az webapp deployment slot swap \
  -g rg-app-prod -n app-orders-prod \
  --slot staging --target-slot production --action swap

If smoke tests fail during preview, abort with --action reset and nothing reaches users:

az webapp deployment slot swap \
  -g rg-app-prod -n app-orders-prod \
  --slot staging --action reset

The warm-up ping configured in Step 2 runs automatically as part of the swap operation — App Service will not complete the swap until the warm-up statuses pass. So you get two gates: your explicit smoke tests during preview, and the platform’s warm-up gate during completion.

Distinguish the two health paths and do not conflate them:

Wire Health Check on the liveness path so the platform pulls unhealthy instances out of rotation independently of deploys:

az webapp config set -g rg-app-prod -n app-orders-prod \
  --generic-configurations '{"healthCheckPath": "/health/live"}'

5. Front Door weighted routing for a gradual edge cutover

A slot swap is binary: 0% then 100%. For high-traffic services you often want to ramp — send 10% to the new version, watch error rates, then ramp to 100%. Azure Front Door Standard/Premium does this with weighted origins in an origin group.

The pattern: register both slots as origins in one origin group. The production slot starts at weight 100, the staging slot at weight 1 (effectively off). After the candidate is validated, you shift weights to ramp traffic, then either complete the cutover or pull it back.

Front Door origin weights are relative, not percentages. Weights of 90 and 10 send roughly 90% and 10% of traffic. Latency-based routing can still influence selection within a priority tier, so for deterministic canary splits keep both origins at the same priority and rely on weight.

Add the staging slot as a second origin (the production slot is assumed already registered):

az afd origin create \
  --resource-group rg-app-prod \
  --profile-name afd-orders \
  --origin-group-name og-orders \
  --origin-name origin-staging \
  --host-name app-orders-prod-staging.azurewebsites.net \
  --origin-host-header app-orders-prod-staging.azurewebsites.net \
  --priority 1 \
  --weight 1 \
  --enabled-state Enabled \
  --https-port 443

Ramp traffic by updating weights. Start small:

# 10% to the new version (relative weights 90 / 10)
az afd origin update -g rg-app-prod --profile-name afd-orders \
  --origin-group-name og-orders --origin-name origin-production --weight 90
az afd origin update -g rg-app-prod --profile-name afd-orders \
  --origin-group-name og-orders --origin-name origin-staging --weight 10

Configure health probes on the origin group so Front Door stops routing to an origin that starts failing — this is your automatic safety net during the ramp:

az afd origin-group update \
  -g rg-app-prod --profile-name afd-orders --origin-group-name og-orders \
  --probe-path /health/live --probe-protocol Https \
  --probe-request-type GET --probe-interval-in-seconds 30 \
  --sample-size 4 --successful-samples-required 3

Traffic Manager (DNS-based, with its own weighted routing method) is an alternative when you need cross-region failover or non-HTTP endpoints. But because it works at DNS, cutover and rollback are gated by client DNS TTL caching — Front Door reweights at the edge and takes effect in seconds, which is what you want for canary control. Use Front Door for HTTP apps; reach for Traffic Manager only for the cross-region or protocol cases it uniquely covers.

There are now two complementary cutover mechanisms: the slot swap (atomic, instance-level, the source of truth for “what is production”) and Front Door weights (gradual, edge-level, for risk-managed ramp). A mature flow uses Front Door weights to validate under real traffic, then performs the slot swap to make the new version the true production slot, then resets weights to 100/0 against the (now-swapped) production origin.

6. Automating the full flow in a pipeline

Here is the end-to-end flow as an Azure DevOps multi-stage pipeline using OIDC (workload identity federation via a service connection), so there are no long-lived secrets. The shape maps directly onto GitHub Actions environments if that is your platform.

# azure-pipelines.yml
trigger:
  branches: { include: [main] }

variables:
  rg: rg-app-prod
  app: app-orders-prod
  slot: staging

stages:
- stage: Build
  jobs:
  - job: build
    pool: { vmImage: ubuntu-latest }
    steps:
      - script: |
          dotnet publish -c Release -o $(Build.ArtifactStagingDirectory)/app
        displayName: Build
      - publish: $(Build.ArtifactStagingDirectory)/app
        artifact: app

- stage: DeployStaging
  dependsOn: Build
  jobs:
  - deployment: deploy_blue
    environment: prod-staging-slot
    pool: { vmImage: ubuntu-latest }
    strategy:
      runOnce:
        deploy:
          steps:
            - download: current
              artifact: app
            - task: AzureWebApp@1
              inputs:
                azureSubscription: sc-prod-oidc   # OIDC service connection
                appName: $(app)
                deployToSlotOrASE: true
                resourceGroupName: $(rg)
                slotName: $(slot)
                package: $(Pipeline.Workspace)/app

- stage: Verify
  dependsOn: DeployStaging
  jobs:
  - job: smoke
    pool: { vmImage: ubuntu-latest }
    steps:
      - task: AzureCLI@2
        inputs:
          azureSubscription: sc-prod-oidc
          scriptType: bash
          scriptLocation: inlineScript
          inlineScript: |
            set -euo pipefail
            HOST="https://${APP}-${SLOT}.azurewebsites.net"
            # Readiness must pass on the candidate before we consider swapping
            for i in $(seq 1 10); do
              code=$(curl -s -o /dev/null -w "%{http_code}" "$HOST/health/ready")
              [ "$code" = "200" ] && echo "ready" && exit 0
              echo "attempt $i -> $code"; sleep 15
            done
            echo "candidate never became ready"; exit 1
        env:
          APP: $(app)
          SLOT: $(slot)

- stage: Swap
  dependsOn: Verify
  jobs:
  - deployment: swap_to_green
    environment: prod   # attach a manual approval check on this environment
    pool: { vmImage: ubuntu-latest }
    strategy:
      runOnce:
        deploy:
          steps:
            - task: AzureCLI@2
              inputs:
                azureSubscription: sc-prod-oidc
                scriptType: bash
                scriptLocation: inlineScript
                inlineScript: |
                  set -euo pipefail
                  az webapp deployment slot swap \
                    -g "$RG" -n "$APP" --slot "$SLOT" --target-slot production
              env:
                RG: $(rg)
                APP: $(app)
                SLOT: $(slot)

The approval gate lives on the prod environment (Azure DevOps environment checks, or a GitHub Actions environment with required reviewers). The pipeline deploys to blue, runs automated verification, pauses for human approval, then performs the swap. The warm-up ping gates the swap itself at the platform level, so even an approved swap will not complete against unhealthy instances.

If you want the gradual Front Door ramp inside the pipeline, insert a stage between Verify and Swap that bumps weights to 10/90, runs a timed observation window querying Front Door metrics or Application Insights failure rate, and only proceeds on a clean window.

7. Instant rollback patterns

The whole point of blue-green is that rollback is fast and boring. There are three rollback levers, and which one you reach for depends on when the regression surfaces.

During preview (before completion): abort. Nothing reached users.

az webapp deployment slot swap -g rg-app-prod -n app-orders-prod --slot staging --action reset

During a Front Door ramp (partial traffic): reweight to zero. Takes effect at the edge in seconds, far faster than a swap or redeploy.

az afd origin update -g rg-app-prod --profile-name afd-orders \
  --origin-group-name og-orders --origin-name origin-production --weight 100
az afd origin update -g rg-app-prod --profile-name afd-orders \
  --origin-group-name og-orders --origin-name origin-staging --weight 0

After a completed swap (100% on new version): swap back. The previous production bits are sitting in the staging slot, so rollback is another swap — not a redeploy.

az webapp deployment slot swap -g rg-app-prod -n app-orders-prod --slot staging --target-slot production

What to test first on rollback: confirm the data layer is compatible with the version you are rolling back to. This is why the expand/contract discipline in Step 3 is non-negotiable — if the failed release ran a destructive migration, a swap-back returns the old code to a schema it cannot read, and you have traded a bad deploy for a hard outage. Verify schema compatibility before you trust swap-back as your rollback.

Enterprise scenario

A payments team ran their orders API behind App Service slots with Front Door weighted ramp, and it worked flawlessly in staging. The first production ramp to 10% triggered a flood of duplicate-charge alerts within ninety seconds. The cause was not the deploy mechanics — it was sticky sessions. They had session affinity enabled on the Front Door origin group, so returning users were pinned to the production origin while new sessions scattered across both. A user who began checkout on the old origin and got reweighted mid-flow hit the new code’s idempotency logic, which keyed off a header the old version never set. Two versions, one payment, no shared idempotency key.

The fix had two parts. First, disable affinity for the canary window so the split is honest and every request is independently routable:

az afd origin-group update -g rg-app-prod \
  --profile-name afd-orders --origin-group-name og-orders \
  --enable-session-affinity false

Second — the real lesson — the idempotency key had to be derived from request content, not a server-set header, so it stayed stable across both versions during the overlap. They moved to a client-supplied Idempotency-Key validated server-side, deployed it as a backward-compatible expand release one sprint ahead of the ramp, and only then resumed weighted cutovers.

The principle: blue-green and canary make two versions serve real users simultaneously. Any state that must be consistent across that boundary — idempotency keys, session tokens, cache key shapes — has to be version-agnostic before you split traffic, not after. Affinity hides the problem in test and detonates it in production.

Going deeper

What actually happens during a swap

A swap is not a redeploy and it is not a config copy — it is a carefully ordered dance the platform runs so that production instances are already warm before they take traffic. Roughly, App Service does this:

  1. Apply the target slot’s slot-specific settings to the source slot. The staging (source) instances receive production’s (target) slot-sticky app settings, connection strings, and auth/CD settings, then restart under that configuration. This is why the candidate ends up validated under production config, not staging config.
  2. Wait for every source instance to restart. If any instance fails to come back, the swap aborts and reverts the source slot — no traffic has moved.
  3. Warm up each instance. App Service issues a request to the app root (/), and if you set WEBSITE_SWAP_WARMUP_PING_PATH / WEBSITE_SWAP_WARMUP_PING_STATUSES, it pings that path and waits for an accepted status on every instance.
  4. Switch the routing rules. Once all instances are warm, the platform exchanges the two slots’ routing — the hostname mapping flips. This is the atomic instant; there is no in-between state where half your users see each version.
  5. Repeat for the other direction so the now-source slot (holding the previous production bits) settles under its own configuration.

The takeaway: the warm-up and restart-under-target-config steps are the real zero-downtime machinery. A trivial /health that returns 200 unconditionally makes steps 3–4 lie to you — the swap completes, and the first real users pay the cold-start tax the warm-up was supposed to absorb.

Which settings stick, and how to override the defaults

Beyond your own slot settings (the ones you mark sticky), the platform keeps a second category sticky by default whether you asked for it or not. Here is the shape of it:

Category Swaps with the release?
General settings (framework version, bitness, WebSockets) Yes
App settings & connection strings Yes — unless marked as slot settings
Handler mappings, path mappings, public certificates Yes
Custom domains, TLS/SSL bindings, private certificates No (sticky)
Scale settings, Always On, IP restrictions No (sticky)
Managed identities, VNet integration, CORS No (sticky)
Diagnostic log settings No (sticky)
Settings whose name ends in _EXTENSION_VERSION No (sticky) unless overridden

That default-sticky set is usually what you want — you do not want staging’s scale rules, certificates, or managed identity landing on production. Two escape hatches exist when the defaults fight you:

The everyday rule stays: connection strings and environment names are slot settings (sticky); feature flags and build metadata travel. These override switches are for the rarer case where a platform default is the exact thing you need to promote.

The x-ms-routing headers: testing in production

App Service has a native, Front-Door-free way to send a slice of real traffic to a slot: percentage-based routing, a.k.a. testing in production.

# Send 20% of clients to the staging slot; 80% stay on production
az webapp traffic-routing set \
  -g rg-app-prod -n app-orders-prod \
  --distribution staging=20

When this is on, App Service pins each routed client with an x-ms-routing-name cookie so they stay on the slot they were assigned for the whole session — a user is never bounced mid-flow between versions. Two overrides matter:

Clear it when the experiment is done:

az webapp traffic-routing clear -g rg-app-prod -n app-orders-prod

This is a legitimate canary built entirely inside App Service — no Front Door required. Its limits: routing is per-client-random (not metric-driven), it is region-local (one plan), and there is no automatic ramp or auto-rollback. For a canary that reacts to error-rate metrics, or one that spans regions, you still want Front Door.

Auto-swap: swap as the last step, without a pipeline step

Auto-swap tells App Service to swap a slot into production automatically the moment a deployment to that slot finishes warming up. Configure it once and every deploy to staging promotes itself:

az webapp deployment slot auto-swap \
  -g rg-app-prod -n app-orders-prod \
  --slot staging --auto-swap-slot production

It is genuinely hands-off continuous deployment, but with two caveats worth internalizing: auto-swap is Windows App Service only (not supported on Linux App Service or Web App for Containers), and it removes the human approval gate. For anything customer-facing I prefer an explicit pipeline swap behind a manual approval (as in Step 6) over auto-swap — you keep the platform’s warm-up guarantee but also keep a person in the loop for production.

Cross-region blue-green, and the DNS-TTL tax

A slot swap is instant because it flips a hostname mapping inside one region’s App Service. To run blue-green across two regions (blue = East US, green = West Europe), you route at the DNS layer with Traffic Manager weighted routing, or at the edge with Front Door. The difference is entirely about how fast a cutover or rollback takes effect:

Mechanism Cutover speed Why
Slot swap Instant Hostname mapping flips inside the region; no client state involved
Front Door weight Seconds Reweighted at Anycast edge POPs; clients re-resolve nothing
Traffic Manager weight Minutes DNS-based — clients cache the answer for the record’s TTL

That last row is the trap. Traffic Manager hands out DNS answers, and clients (and their resolvers) cache them for the record’s TTL. Drop a region’s weight to zero and users who already resolved it keep hitting it until their cache expires. Lower the TTL (e.g. 30–60s) before a planned cutover so rollback is not gated by a stale cache — but you can never make DNS as instant as a slot swap. The pattern that gives you both: use Traffic Manager or Front Door to steer between regions, and a slot swap within each region for the actual instant version flip.

Blue-green on containers and AKS

Slots are an App Service PaaS convenience — the platform hands you a second environment and an atomic swap for free. On Web App for Containers slots still work, but auto-swap does not, and warm-up matters more because container cold starts are heavier. On AKS there is no slot primitive at all; you rebuild the pattern from Kubernetes objects:

The mental model transfers cleanly: two identical environments, one live, an atomic (or gradual) switch, and rollback by switching back. Only the machinery differs.

Verify

# Slot settings are sticky (env-specific keys must show slotSetting: true)
az webapp config appsettings list -g rg-app-prod -n app-orders-prod --slot staging \
  --query "[?slotSetting].name" -o tsv

# Candidate readiness passes on the staging slot before any swap
curl -s -o /dev/null -w "%{http_code}\n" \
  https://app-orders-prod-staging.azurewebsites.net/health/ready    # expect 200

# Confirm which version each slot currently serves (expose build SHA at an endpoint)
curl -s https://app-orders-prod.azurewebsites.net/version
curl -s https://app-orders-prod-staging.azurewebsites.net/version

# Front Door origin weights are where you expect during/after a ramp
az afd origin list -g rg-app-prod --profile-name afd-orders \
  --origin-group-name og-orders --query "[].{name:name,weight:weight,priority:priority}" -o table

# Front Door is routing to a healthy origin end to end
curl -s -o /dev/null -w "%{http_code}\n" https://<your-frontdoor-endpoint>/health/live

A swap is correct when production serves the new build SHA, the previous SHA is now in staging (ready for swap-back), and Front Door reports both origins healthy with the expected weights.

Release checklist

Pitfalls nobody documents

Build the swap-with-preview flow first, make warm-up gate on a readiness check that means something, and rehearse all three rollback levers before you need them. Done that way, a bad release is a non-event: you swap back in seconds and debug at leisure, instead of redeploying into a live incident.

Practice challenges

Work these against a non-production app. Commands are real and schema-correct; this course has no live Azure subscription attached, so treat any output as representative and adapt names/IDs to your environment.

1. Create a staging slot (beginner). You have app-shop-dev in rg-shop-dev on a Standard plan. Add a slot named staging.

<details> <summary>Solution</summary>

az webapp deployment slot create \
  -g rg-shop-dev -n app-shop-dev --slot staging

Why: slots require Standard tier or higher; on Basic/Free the command fails. The slot is a full addressable copy at app-shop-dev-staging.azurewebsites.net. </details>

2. Keep a connection string from travelling (beginner). Mark the staging slot’s SqlConnection so it stays pinned to the slot during a swap.

<details> <summary>Solution</summary>

az webapp config appsettings set \
  -g rg-shop-dev -n app-shop-dev --slot staging \
  --slot-settings SqlConnection="<staging-conn-string>"

Why: --slot-settings (not --settings) sets slotSetting: true, so the value is sticky and does not swap into production. Environment-specific config should always be a slot setting. </details>

3. Gate the swap on readiness (intermediate). Make the swap wait until /health/ready returns 200 or 202 on every candidate instance.

<details> <summary>Solution</summary>

az webapp config appsettings set \
  -g rg-shop-dev -n app-shop-dev --slot staging \
  --slot-settings \
    WEBSITE_SWAP_WARMUP_PING_PATH=/health/ready \
    WEBSITE_SWAP_WARMUP_PING_STATUSES="200,202"

Why: these two settings turn the swap into a gated operation — App Service will not complete it until warm-up passes on all instances. The path must check real dependencies, not return 200 blindly. </details>

4. Preview before you commit (intermediate). Apply production config to staging without moving traffic, then abort the preview cleanly.

<details> <summary>Solution</summary>

# Phase 1: apply prod config to staging, no traffic moved
az webapp deployment slot swap -g rg-shop-dev -n app-shop-dev \
  --slot staging --target-slot production --action preview

# ...smoke test the staging host, now running prod config...

# Abort — nothing ever reached users
az webapp deployment slot swap -g rg-shop-dev -n app-shop-dev \
  --slot staging --action reset

Why: --action preview is a two-phase swap; reset cancels it. You validated the candidate under production config with zero user exposure. </details>

5. Poor-man’s canary inside App Service (advanced). Send 15% of real users to staging, pin your own browser to the staging slot via URL, then clear the split.

<details> <summary>Solution</summary>

az webapp traffic-routing set -g rg-shop-dev -n app-shop-dev \
  --distribution staging=15
# Force your own browser to the candidate regardless of the 15%:
#   https://app-shop-dev.azurewebsites.net/?x-ms-routing-name=staging
# Force back to production:  ?x-ms-routing-name=self
az webapp traffic-routing clear -g rg-shop-dev -n app-shop-dev

Why: percentage routing pins each client with the x-ms-routing-name cookie; the query string overrides it for testing. No Front Door needed for a region-local canary. </details>

6. Roll back a completed swap (advanced). Production is now on the bad build. Restore the previous version, and state the one precondition that makes this safe.

<details> <summary>Solution</summary>

az webapp deployment slot swap -g rg-shop-dev -n app-shop-dev \
  --slot staging --target-slot production

Why: after a swap, the previous production bits sit in staging, so rollback is another swap — seconds, not a redeploy. The precondition: the database must still be compatible with the old code (expand/contract discipline). If the bad release ran a destructive migration, swap-back returns old code to a schema it cannot read. </details>

Common beginner mistakes

Glossary

AzureBlue-GreenApp ServiceAzure Front DoorCI/CD
Need this built for real?

Vinod is a Senior Cloud Architect (22+ yrs) — available for Azure / AWS / GCP architecture, landing zones, and migrations.

Work with me

Comments