Azure Compute

Setting Up Azure App Service Deployment Slots: Swap, Warm-Up and Slot-Sticky Settings

You push a new build to production at 11am, and for the next ninety seconds the site throws slow first requests, 502s, or a half-loaded page while the worker boots the new code — and every user who arrives in that window feels it. The fix isn’t a faster deploy; it’s deploying somewhere else first. Azure App Service deployment slots are live, addressable copies of your web app that share the same App Service plan. You deploy to a staging slot, let it warm up against real configuration, then swap it into production — and the swap is a routing change, not a restart, so production users never hit a cold worker or a half-deployed app. Done right, a deploy goes from “ninety seconds of pain” to “zero downtime, instant rollback.”

This is the implementation guide. We build the whole thing end to end: create a web app, add a staging slot, configure swap warm-up so the swap only completes once the new code answers a health path, decide which app settings are slot-sticky (stay put through a swap) versus which travel with the code, run a real swap in the portal and in az CLI, express the identical setup as Bicep, then practise the two things that save you in production — rollback (swap back) and traffic splitting for canary testing. Every step has the exact command, the expected output, and a validation check, because a slot you think is configured but isn’t is worse than no slot at all.

By the end you will run swaps without thinking twice. You will know exactly what a swap does under the hood (and why it’s near-zero-downtime), which settings must be marked sticky or they’ll follow your code into the wrong environment, why a swap can fail mid-flight and how to recover, and how much slots actually cost (on the right tier, nothing extra — they run on the plan you already pay for). The slot is one of the highest-leverage features in App Service, and most teams either don’t use it or use it wrong; this gets you to the correct, boring, repeatable swap.

What problem this solves

In-place deployment has a structural flaw: the instance serving production traffic is the same instance you’re changing. While the new code loads — runtime boot, JIT, DI build, connection-pool prime, for containers an image pull — that worker can’t serve well. On a single instance the app may go fully 503 during the restart; on a warm-but-changing instance you get slow first requests and occasionally 502 if a probe or upstream times out the still-booting worker. The deploy “worked,” but your users absorbed the cost — and if the new build is broken you’re now broken in production with roll-forward as your only fast option.

What breaks without slots: every release is a small outage you’ve normalised. Teams schedule deploys for “low traffic” windows, batch releases to amortise the pain (making each riskier), and treat rollback as “redeploy the old artifact and wait” — minutes of broken production while the old code rebuilds. Configuration changes are worse: flip an app setting in place and the app recycles immediately, so a one-character edit causes the same restart blip as a full deploy.

Who hits this: anyone running a customer-facing web app or API who deploys more than rarely. It bites hardest on single-instance plans (every restart is a visible 503), apps with meaningful cold-start cost (large .NET/JVM apps, custom containers), and frequent CI/CD where “a blip per deploy” adds up to eroded availability. Slots turn the deploy from a write-to-production into a swap-into-production: warm the new code in isolation, validate it on its own URL, make it live atomically — and keep the previous version one swap away for instant rollback.

To frame the field before the build, here is what a slot gives you, the pain it removes, and the smallest tier that enables it:

Capability Pain it removes How it works Smallest tier
Staging slot Deploying onto live production workers A second live copy of the app on the same plan Standard (5 slots); Basic limited
Swap Restart/cold-start blip on every deploy Warm instances move to production by routing change Standard (S1+)
Swap with warm-up Production briefly serving cold workers post-swap Platform pings a path on staging until healthy Standard (S1+)
Slot-sticky settings Prod connection strings following code into staging Sticky settings stay bound to the slot Standard (S1+)
Rollback (swap back) Minutes of broken prod while old code redeploys One reverse swap restores the previous version Standard (S1+)
Traffic splitting All-or-nothing releases Route a % of prod traffic to a slot for canary Standard (S1+)

Learning objectives

By the end of this article you can:

Prerequisites & where this fits

You should already understand App Service basics: an App Service plan is the set of VM workers (an SKU such as B1, S1, P1v3) you rent, and one or more web apps run on that plan sharing its CPU, memory and instance count. You should be comfortable running az in Cloud Shell, reading JSON output, and editing a small Bicep file. Knowing how App Service handles app settings (key/value pairs injected as environment variables) and Always On helps, and a passing familiarity with HTTP status codes makes the warm-up and rollback sections click.

This sits in the Compute / deployment-and-release track. It builds on the platform fundamentals in the Azure App Service Deep Dive: Plans, Scaling, Slots, TLS & Networking and the tier decisions in Azure App Service Plans & Tiers Explained — because slots are a tier feature (Standard and above for the full five). It pairs with the hardening patterns in Hardening Azure App Service: VNet Integration, Private Endpoints & Zero-Downtime Slots, and when a swap goes wrong the diagnostic moves come from Troubleshooting Azure App Service: 502/503, Cold Starts & Restart Loops. Secrets and config that should be slot-sticky usually live in Azure Key Vault and Azure App Configuration.

Core concepts

Five ideas make every later step obvious.

A slot is a full, live web app — same plan, different hostname. Add a staging slot to app-shop and you get a second running app at app-shop-staging.azurewebsites.net with its own config, deployment and logs — but running on the same App Service plan instances as production. It is not a separate environment you pay extra for; it shares the plan’s workers. That sharing is the whole trick: because staging instances are already warm on the same plan, a swap can move them to production without a cold start.

A swap is a routing change, not a deployment. This is the idea most people miss. Swapping staging into production does not copy files or restart production. App Service applies the target slot’s sticky settings to the source slot’s instances, warms them up, then switches the internal routing so the instances that served staging now serve production — and vice versa. The warm instances simply change which hostname they answer; no worker boots from cold during the cutover, so a warmed swap is near-zero-downtime. What was production now sits in the staging slot — which is exactly why rollback is just another swap.

Settings come in two flavours: travelling and sticky. By default an app setting or connection string travels with the slot’s content through a swap — set LOG_LEVEL=Debug in staging and after the swap production has it too. That is right for code-coupled config (feature flags shipped with the build) but catastrophic for environment config: you do not want the staging database connection string riding into production. Mark a setting as a deployment slot setting (slot-sticky) and it stays bound to its slot and never moves. So DB_CONNECTION is sticky (each slot keeps its own); FEATURE_NEW_CHECKOUT travels (it ships with the code).

Warm-up makes the swap honest. Staging instances are warm as staging, but the moment they take production traffic they may need to re-resolve production sticky settings, reconnect to the production database, or re-JIT a path. Swap warm-up sends requests to a path you choose (WEBSITE_SWAP_WARMUP_PING_PATH) on the slot being swapped in, completing the swap only once that path returns an acceptable status (WEBSITE_SWAP_WARMUP_PING_STATUSES). If the new code can’t answer it, the swap aborts and rolls itself back — your protection against swapping a broken build live.

Slots are finite and tier-gated. Standard (S1–S3) gives up to 5 slots (including production); Premium v3 (P1v3–P3v3) and Isolated give up to 20; Basic (B1–B3) has limited slot support and Free/Shared have none. Each slot is a full app on the plan, so more slots means more apps competing for the plan’s CPU/RAM — slots aren’t free in resource terms even though they don’t add to the plan bill. Plan for what you use: usually just production + staging, sometimes a canary.

The vocabulary in one table

Pin down every moving part before the build. The glossary repeats these for lookup; this is the mental model side by side:

Concept One-line definition Where it lives Why it matters to a swap
Deployment slot A live copy of the app on the same plan On the plan, own hostname The thing you swap into production
Production slot The default slot serving live traffic The web app itself The swap target
Source / target slot Source = swapping in; target = swapping into Swap operation Defines swap direction and rollback
Swap Warm instances change which hostname they serve Deployment slots blade The near-zero-downtime cutover
Swap with preview Two-phase swap; phase 1 applies target config to source Swap dialog / --action preview Validate against prod config before committing
Slot-sticky setting A “deployment slot setting” that stays on its slot Configuration blade Stops prod secrets riding into staging
Warm-up path WEBSITE_SWAP_WARMUP_PING_PATH pinged before swap completes App setting Aborts the swap if new code is unhealthy
Auto-swap Slot auto-swaps into a target after each deploy Slot config Hands-off staging→prod promotion
Traffic splitting Route a % of prod traffic to a slot Deployment slots blade Canary / gradual rollout
Rollback A reverse swap restoring the previous version Deployment slots blade Instant recovery from a bad release

What a swap actually does, step by step

Pinning down the exact sequence explains every later behaviour — why settings move or don’t, why warm-up matters, why rollback is instant. Swapping source = staging into target = production runs roughly this:

Phase What the platform does Why it matters
1. Apply target config to source Production’s sticky settings + slot config are applied to the staging instances (which restart) Staging instances become “production-shaped”
2. Warm up Pings WEBSITE_SWAP_WARMUP_PING_PATH on the now-prod-configured staging instances The new code proves it can serve with prod config
3. Health gate Accepted status → proceed; else abort + roll back Your safety net against a broken build
4. Switch routing The internal routing for the two slots is exchanged The actual cutover — no cold start
5. Old prod lands in staging The instances that were production now answer the staging hostname Why rollback is just a reverse swap

Two consequences fall straight out. Phase 1 restarts the incoming instances in staging — fine, because they’re warming as staging, not serving production. And because the cutover (phase 4) is a routing switch over already-warmed instances, production never serves a cold worker — provided your warm-up path exercises the paths that matter. A swap with no warm-up path still switches routing over warm instances but skips the proof the new code can serve production config; beyond a trivial app, configure warm-up. The whole thing reduces to: a swap exchanges which set of warm instances answers which hostname, after making the incoming set prove itself.

Slot-sticky settings: the part that bites

Get this wrong and a swap promotes your staging database, storage account, or App Insights key into production — or drags production’s into staging. The rule is simple but the defaults are not what you’d guess.

Travelling vs sticky — the default and the override

By default, app settings and connection strings travel with the slot’s content through a swap — they’re paired to the code, so when staging’s content moves to production, staging’s non-sticky settings go with it. That’s right for config that’s part of the build (a feature flag, a release log level) and wrong for config that defines which environment you are (database, cache, queue, secrets, telemetry). To pin a setting to its slot, mark it a deployment slot setting (the portal checkbox; --slot-settings in the CLI) — it then stays on the slot through a swap. So you set DB_CONNECTION sticky on each slot pointing at that environment’s database, and a swap leaves each where it should be.

The decision table — what to mark sticky and what to let travel:

Setting type Example Sticky? Reasoning
Environment endpoint DB / cache / queue connection string Sticky Each environment must keep its own backend
Telemetry target APPLICATIONINSIGHTS_CONNECTION_STRING (per-slot) Sticky Keep staging telemetry out of prod (or vice versa)
Secret / Key Vault ref pointing to env-specific secret @Microsoft.KeyVault(...staging-vault...) Sticky Prevent staging secrets reaching prod
Slot identifier ASPNETCORE_ENVIRONMENT / ENV_NAME Sticky The slot’s identity must not travel
Feature flag shipped with the build FEATURE_NEW_CHECKOUT Travel It’s part of the code you’re promoting
Release-coupled log level LOG_LEVEL for this build Travel Logically belongs to the build
Build/runtime config WEBSITES_PORT, LINUX_FX_VERSION (image tag) Travel The new image/port is what you’re promoting

One category is always slot-specific and never swapped, whether you mark it or not — the platform treats these as belonging to the slot:

Always slot-specific (never swaps) Why
Publishing endpoints / credentials Each slot has its own deploy target
Custom domain names Bound to a specific slot’s hostname
Non-public certificates & TLS/SSL settings Tied to the slot’s bindings
Scale (instance count) settings Slot-level scale config
Always On, IP/SCM access restrictions Slot-level platform config
Diagnostic log settings Per-slot logging
Managed identities Each slot has its own identity

Everything else swaps by default unless you mark it sticky — general app settings, connection strings, handler mappings, public certificates and path mappings. Mark the ones that define the environment (DB, cache, secrets) sticky; leave the rest to travel.

Why the default catches people

The trap: a developer sets DB_CONNECTION on staging only (not sticky), points it at the staging database, tests, then swaps. Because the setting travels, production now has the staging connection string — reading and writing the staging database, often silently until data looks wrong. The fix is to set environment endpoints on both slots and mark them sticky from the start, so the value binds to the slot, not the content. The reverse trap: someone marks a feature flag sticky, swaps, and is baffled it didn’t promote — sticky settings, by design, don’t move. A quick check: run a swap with preview (next section) and read the previewed production settings before committing.

Warm-up: making the swap refuse a broken build

Warm-up turns a swap from “switch routing and hope” into “switch routing only if the new code answers.” It uses two app settings on the slot being warmed:

Setting What it does Default Valid values Notes
WEBSITE_SWAP_WARMUP_PING_PATH Path the platform requests during a swap before completing / (root) any in-app path, e.g. /healthz Must return an accepted status or the swap aborts
WEBSITE_SWAP_WARMUP_PING_STATUSES Status codes that count as “warmed/healthy” 200, 202 comma list, e.g. 200,202,204 Widen only if your warm path legitimately returns non-200

During a swap, the platform sends warm-up requests to that path on the incoming instances (already configured with production’s sticky settings). An accepted status completes the swap; a non-accepted status or timeout aborts and rolls it back — production is never cut over to a build that can’t serve. This is distinct from the health check (healthCheckPath), which evicts unhealthy instances during normal operation; warm-up specifically gates the swap.

Design the warm-up path the same way you design a health path — shallow and honest, exercising the code paths a swap must validate without depending on something that’s allowed to be briefly down:

Warm-up path should… Include Never include
Prove the app can serve with prod config Config load, DI resolution, a cheap cached read A slow report or aggregate query
Touch the required backend so reconnection happens during warm-up A fast ping to the production DB the app can’t serve without A third-party payment/email API
Return fast and deterministically A dedicated /warmup or /healthz endpoint The home page if it’s heavy or personalised
Anything that can hang and stall the swap

The subtle, high-value point: because phase 1 applies production’s sticky settings to the incoming instances, the warm-up path runs against production configuration — so a green warm-up means the new code can serve with the real production database/cache/secrets, not staging’s.

Swap modes: direct, preview (two-phase), and auto-swap

Three ways to trigger the cutover; pick by how much control you want.

Mode How it’s triggered When to use Trade-off
Direct swap One action: az webapp deployment slot swap / portal “Swap” Routine releases with a trusted warm-up path You trust warm-up to gate it
Swap with preview Phase 1 --action preview; validate; phase 2 --action swap (or reset) Test the new code with production config on the staging URL first Two steps; staging temporarily runs prod config
Auto-swap Slot auto-swaps into a target after each successful deploy Fully automated pipelines for low-risk apps No manual gate; warm-up is the only safety

Swap with preview (the two-phase swap)

The safest way to validate a release. Phase 1 (preview) applies the target (production) configuration to the source (staging) slot and restarts it — but does not switch routing. Now app-shop-staging.azurewebsites.net runs the new code with production settings, so you can smoke-test the exact thing about to go live. Phase 2 (complete) finishes the routing switch; or reset reverts staging to its own configuration with no impact on production.

# Phase 1: apply production config to staging and pause (no routing switch yet)
az webapp deployment slot swap --name app-shop --resource-group rg-shop \
  --slot staging --target-slot production --action preview

# ... smoke-test https://app-shop-staging.azurewebsites.net (now running prod config) ...

# Phase 2a: complete the swap
az webapp deployment slot swap --name app-shop --resource-group rg-shop \
  --slot staging --target-slot production --action swap

# Phase 2b (instead): cancel and revert staging to its own config
az webapp deployment slot swap --name app-shop --resource-group rg-shop \
  --slot staging --target-slot production --action reset

Auto-swap (hands-off promotion)

Auto-swap swaps a slot into a target after every successful deployment to it — deploy to staging and the platform warms and swaps it into production, no manual step. Convenient for low-risk apps, dangerous where you want a human gate (a bad build that passes warm-up still goes live). Don’t combine it with manual preview testing on the same slot, and note it’s not supported on Linux/custom-container apps the way it is on Windows.

# Enable auto-swap on the staging slot, targeting production (Windows app)
az webapp deployment slot auto-swap --name app-shop --resource-group rg-shop \
  --slot staging --auto-swap-slot production

Traffic splitting and canary releases

A swap is all-or-nothing — 100% of production traffic moves at once. Sometimes you want to route a small percentage of production traffic to a slot first (a canary), watch error rates and latency, then roll out fully. App Service supports this directly: set a routing percentage on a slot and that share of production requests goes to it.

# Send 10% of production traffic to the staging slot (canary)
az webapp traffic-routing set --name app-shop --resource-group rg-shop \
  --distribution staging=10

# Inspect current routing
az webapp traffic-routing show --name app-shop --resource-group rg-shop

# Stop the split (send everything back to production)
az webapp traffic-routing clear --name app-shop --resource-group rg-shop

Two mechanics to know. A user routed to the slot is pinned there for the session via the x-ms-routing-name cookie, so they don’t flip versions mid-session (self-select production with ?x-ms-routing-name=self). And traffic splitting is not a swap — it routes a fraction of users to the staging hostname’s content while leaving production as-is; the canary flow below ends with a swap to promote.

Canary control Command / mechanism Effect
Start canary az webapp traffic-routing set --distribution staging=10 10% of prod traffic to staging content
Ramp up re-run with staging=25, then 50 Gradually shift load while watching telemetry
Pin/opt-out x-ms-routing-name cookie / query string Keep a user on one version per session
Promote az webapp deployment slot swap … Make staging the full production version
Abort az webapp traffic-routing clear Send everyone back to production instantly

Architecture at a glance

The diagram traces a deploy through slots, left to right, so you can see why production never goes cold. Your CI/CD pushes the new build to the staging slot — a full live app at app-shop-staging.azurewebsites.net on the same plan instances as production, carrying travelling settings (feature flags shipped with the build) and its own slot-sticky settings (its staging database and telemetry). On swap, the platform applies production’s sticky settings to the staging instances, warms them up by pinging WEBSITE_SWAP_WARMUP_PING_PATH against production config, and only on an accepted status switches the internal routing so the warmed instances answer app-shop.azurewebsites.net. No worker boots from cold during the cutover.

Notice what lands where: the instances that were production now sit in the staging slot holding the previous version — which is why rollback is just a reverse swap. The numbered badges mark the four places this goes wrong: a sticky-vs-travelling setting sending the wrong config across (1); a warm-up path that fails or times out, aborting the swap (2); slot exhaustion or a cold-swap blip when warm-up is skipped (3); and the rollback path itself, your recovery when a build passes warm-up but misbehaves under real traffic (4). The whole method is in the picture: warm the new code in isolation, prove it against production config, switch routing atomically, keep the old version one swap away.

Azure App Service deployment-slot swap architecture, left to right: a CI/CD pipeline deploys a new build to a staging slot that runs on the same App Service plan as the production slot and carries both travelling app settings and slot-sticky settings; a swap operation applies production's sticky settings to the staging instances, runs swap warm-up by pinging WEBSITE_SWAP_WARMUP_PING_PATH against production configuration, and only on an accepted status switches internal routing so the warmed staging instances now serve the production hostname while the previous production version lands in the staging slot for instant rollback; numbered badges mark sticky-vs-travelling setting mistakes, warm-up failure aborting the swap, slot exhaustion or cold-swap blips, and the reverse-swap rollback path

Real-world scenario

Finhaven, a fintech, runs its customer dashboard API on Azure App Service: a .NET 8 app on a 2× S1 Standard plan in Central India, deployed from GitHub Actions ~ten times a week. For a year they deployed in place. Each deploy caused a 30–90 second window of slow first requests and the occasional 502 as warm instances reloaded the new build — tolerable individually, but at ten deploys a week it was a steady drip of “the dashboard was slow around 2pm” tickets, and measured availability sat at an embarrassing 99.4%.

The push to fix it came after a Friday deploy shipped a broken database migration path. The app started, served 500s on every dashboard call, and the only rollback was “revert the commit, rebuild, redeploy, wait” — eleven minutes of broken production. The post-incident action item: stop deploying onto production workers.

They set up slots properly. They added a staging slot, moved the deploy to target staging instead of production, and marked the environment-specific settings — DB_CONNECTION, REDIS_CONNECTION, the production APPLICATIONINSIGHTS_CONNECTION_STRING, and ASPNETCORE_ENVIRONMENT — as deployment slot settings (sticky) on both slots, so the staging build couldn’t drag staging’s backends into production. They added a dedicated /warmup endpoint that loaded config, resolved the DI graph, and did one cached read against the production database, then set WEBSITE_SWAP_WARMUP_PING_PATH=/warmup. The pipeline’s final step became a swap with preview: deploy to staging, run a smoke-test job against the staging URL (validating against prod settings in the preview phase), complete the swap — and on any smoke-test failure, --action reset instead.

The first real test came two weeks later: another bad migration. This time it deployed to staging, the smoke-test job got 500s, and the pipeline ran --action reset — production was never touched. The week after, a good build with a slow cold path was caught by warm-up timing out; they widened the warm-up tolerance and the next swap completed cleanly. Measured availability climbed to 99.95% within a month, the “slow around 2pm” tickets stopped, and the rollback drill went from “eleven minutes and a rebuild” to “one reverse swap, under thirty seconds.” Cost stayed flat at ₹14,000 — the staging slot ran on the same plan they already paid for. The lesson on the wall: “Deploy to a slot, prove it against prod config, then swap. Production is a routing target, not a deploy target.”

The before/after, because the shape of the change is the lesson:

Dimension Before (in-place deploy) After (slot + warm-up swap)
Deploy impact 30–90 s slow/502 per deploy Near-zero; warm instances switch in
Bad build reaches prod? Yes, immediately No — caught in staging by smoke-test + warm-up
Rollback Revert + rebuild + redeploy (~11 min) One reverse swap (< 30 s)
Config safety Staging DB once leaked to prod Sticky settings pinned per slot
Measured availability 99.4% 99.95%
Extra cost ₹0 (same plan)

Advantages and disadvantages

Slots are close to free in money and large in benefit, but not zero-effort and not a fix for everything. Weigh it honestly:

Advantages Disadvantages
Near-zero-downtime deploys — warm instances switch in, no cold start in production Only on Standard+ (full 5 slots); Free/Shared have none, Basic is limited
Instant rollback — a reverse swap restores the previous version in seconds Slots share the plan’s CPU/RAM; running a busy staging slot competes with production
Warm-up gates the swap — a build that can’t serve aborts the swap automatically Sticky-vs-travelling settings are a real footgun until you understand the default
Swap-with-preview lets you validate the new code against production config before committing A swap is all-or-nothing; without traffic splitting there’s no gradual rollout
No extra bill — slots run on the plan you already pay for Database/schema changes still need their own forward/backward-compatible strategy
Traffic splitting enables real canary releases on the same primitive Auto-swap removes the human gate; a build passing warm-up still goes live
Config changes go through staging, so a one-character setting edit no longer restarts production More moving parts in the pipeline (slot target, sticky flags, warm-up path to maintain)

When each matters: slots are right for any customer-facing app deployed more than rarely — the availability and rollback wins are immediate, and largest on frequent CI/CD and cold-start-heavy apps. They matter less if you deploy monthly and tolerate a maintenance window, or on Free/Shared (where you can’t have them anyway). The two disadvantages that bite hardest — sticky settings and stateful schema changes — are addressed by discipline (mark environment config sticky from day one) and a separate database-migration strategy (expand/contract), not by the slot feature itself.

Hands-on lab

This is the centrepiece. You will build the full slot workflow end to end three ways — portal, az CLI, and Bicep — on a free-tier-friendly setup (we use S1 for the full slot feature; delete at the end). Each step states the command, the expected output, and a validation check. Run the CLI track in Cloud Shell (Bash).

Part A — Prerequisites and a production app (CLI)

Step 1 — Set variables and create a resource group. Pick unique names; $RANDOM keeps the hostname globally unique.

RG=rg-slots-lab
LOC=centralindia
PLAN=plan-slots-lab
APP=app-slots-$RANDOM        # globally-unique hostname
echo "App will be: $APP"
az group create -n $RG -l $LOC -o table

Expected: a table row for the resource group with ProvisioningState = Succeeded. Validate: az group exists -n $RG returns true.

Step 2 — Create an S1 (Standard) Linux plan. Standard is the tier that gives the full 5 slots and slot-swap warm-up.

az appservice plan create -n $PLAN -g $RG --is-linux --sku S1 -o table

Expected: a plan row with Sku = S1, Kind = linux. Validate:

az appservice plan show -n $PLAN -g $RG --query "{sku:sku.name, tier:sku.tier, kind:kind}" -o table
# sku=S1, tier=Standard, kind=linux  → slots available

Step 3 — Create the production web app on a built-in runtime. We use a built-in .NET runtime so there’s no container/port complexity; the slot mechanics are identical for containers.

az webapp create -n $APP -g $RG -p $PLAN --runtime "DOTNETCORE:8.0" -o table

Expected: a web app row with DefaultHostName = $APP.azurewebsites.net. Validate: browse to https://$APP.azurewebsites.net — you get the default App Service landing page (HTTP 200). The app is live (the production slot).

Step 4 — Add production-safe settings, including a sticky one. Set Always On (S1 supports it), an ENV_NAME we’ll make sticky to prove it doesn’t move, and a RELEASE value we’ll let travel.

az webapp config set -n $APP -g $RG --always-on true -o none

# A sticky (slot) setting: ENV_NAME must stay bound to whichever slot it's set on
az webapp config appsettings set -n $APP -g $RG \
  --slot-settings ENV_NAME=production -o none

# A travelling setting: RELEASE moves with the content during a swap
az webapp config appsettings set -n $APP -g $RG \
  --settings RELEASE=v1.0.0 -o none

Expected: no error (we used -o none). Validate: list the settings and confirm ENV_NAME is marked sticky and RELEASE is not:

az webapp config appsettings list -n $APP -g $RG \
  --query "[?name=='ENV_NAME' || name=='RELEASE'].{name:name, value:value, slotSetting:slotSetting}" -o table
# ENV_NAME  production  True      ← sticky
# RELEASE   v1.0.0      False     ← travels

Part B — Create and deploy a staging slot (CLI)

Step 5 — Create the staging slot. A slot can clone the production app’s config, which is the usual starting point.

az webapp deployment slot create -n $APP -g $RG --slot staging \
  --configuration-source $APP -o table

Expected: a row for the slot. Validate: the slot is live on its own hostname:

az webapp deployment slot list -n $APP -g $RG --query "[].{slot:name, host:defaultHostName, state:state}" -o table
# staging  app-slots-XXXX-staging.azurewebsites.net  Running

Browse to https://$APP-staging.azurewebsites.net — the default page renders (the slot is a real, running app).

Step 6 — Differentiate the slots so a swap is observable. Set the slot’s sticky ENV_NAME to staging (proves stickiness — it must not move), and bump the travelling RELEASE on the slot to v1.1.0 (proves travelling — it should move on swap).

# Sticky on the slot: staging keeps ENV_NAME=staging
az webapp config appsettings set -n $APP -g $RG --slot staging \
  --slot-settings ENV_NAME=staging -o none

# Travelling on the slot: this new RELEASE should ride into production on swap
az webapp config appsettings set -n $APP -g $RG --slot staging \
  --settings RELEASE=v1.1.0 -o none

Validate: confirm the slot’s values:

az webapp config appsettings list -n $APP -g $RG --slot staging \
  --query "[?name=='ENV_NAME' || name=='RELEASE'].{name:name, value:value, slotSetting:slotSetting}" -o table
# ENV_NAME  staging   True
# RELEASE   v1.1.0    False

Step 7 — Configure swap warm-up on the slot. Point warm-up at the app’s root (the default landing page returns 200) so the swap only completes when the slot answers. In a real app, use a dedicated /warmup or /healthz.

az webapp config appsettings set -n $APP -g $RG --slot staging \
  --slot-settings \
    WEBSITE_SWAP_WARMUP_PING_PATH=/ \
    WEBSITE_SWAP_WARMUP_PING_STATUSES=200 -o none

Note: we mark the warm-up settings sticky so they stay on the slot and don’t travel into production. Validate: they’re present and sticky:

az webapp config appsettings list -n $APP -g $RG --slot staging \
  --query "[?starts_with(name,'WEBSITE_SWAP_WARMUP')].{name:name, value:value, slotSetting:slotSetting}" -o table

Part C — Swap with preview, then complete (CLI)

Step 8 — Record the current production state (baseline). So you can prove the swap worked.

az webapp config appsettings list -n $APP -g $RG \
  --query "[?name=='ENV_NAME' || name=='RELEASE'].{name:name, value:value}" -o table
# Before swap → ENV_NAME=production, RELEASE=v1.0.0

Step 9 — Phase 1: swap with preview. This applies production’s sticky config to the staging instances and pauses — no routing switch yet — so you can validate the incoming build against production config on the staging URL.

az webapp deployment slot swap -n $APP -g $RG \
  --slot staging --target-slot production --action preview -o table

Expected: the command reports the preview swap started. Validate: browse https://$APP-staging.azurewebsites.net — it’s now running with production’s sticky settings applied, while production traffic is untouched. (In a real pipeline this is where your smoke-test job runs.)

Step 10 — Phase 2: complete the swap. Commit the cutover. Warm-up runs against the production-configured instances; only an accepted status completes it.

az webapp deployment slot swap -n $APP -g $RG \
  --slot staging --target-slot production --action swap -o table

Expected: the swap completes without error (it may take 30–60+ seconds while warm-up runs). Validate: the travelling setting moved but the sticky setting did not:

az webapp config appsettings list -n $APP -g $RG \
  --query "[?name=='ENV_NAME' || name=='RELEASE'].{name:name, value:value}" -o table
# After swap → ENV_NAME=production  (sticky: did NOT move — correct)
#              RELEASE=v1.1.0       (travelled: moved with content — correct)

This single check is the whole lesson: ENV_NAME stayed production (sticky), RELEASE became v1.1.0 (travelled). That is exactly the behaviour you want — environment identity pinned, build-coupled config promoted.

Part D — Rollback and canary (CLI)

Step 11 — Roll back with a reverse swap. The previous production version is sitting in the staging slot; swap it back.

az webapp deployment slot swap -n $APP -g $RG \
  --slot staging --target-slot production --action swap -o table

az webapp config appsettings list -n $APP -g $RG \
  --query "[?name=='RELEASE'].{name:name, value:value}" -o table
# RELEASE back to v1.0.0 → rollback succeeded in seconds, no redeploy

Expected: RELEASE is back to v1.0.0. Validate: you restored the previous version with a single swap and no rebuild — this is the rollback drill.

Step 12 — Try a canary traffic split. Send 20% of production traffic to staging, inspect it, then clear it.

az webapp traffic-routing set -n $APP -g $RG --distribution staging=20 -o table
az webapp traffic-routing show -n $APP -g $RG -o table
# Confirms 20% routed to staging
az webapp traffic-routing clear -n $APP -g $RG -o none

Expected: the split is reported, then cleared. Validate: az webapp traffic-routing show after clear returns no active split.

The lab steps mapped to what each proves:

Step What you did What it proves
5 Create staging slot A slot is a full live app on the same plan
6 Sticky ENV_NAME + travelling RELEASE The two setting flavours behave differently
7 Warm-up settings on the slot A swap can be gated by a health path
9 Swap with preview You can validate against prod config before committing
10 Complete swap Sticky stays, travelling moves — the core behaviour
11 Reverse swap Rollback is one swap, no redeploy
12 Traffic split Canary is the same primitive

Part E — The Bicep version (idempotent, reviewable)

The same setup as infrastructure-as-code. This declares the plan, the production app with a sticky and a travelling setting, the staging slot with its own sticky settings and warm-up config. (The swap itself is an operation, not declarative state, so you run it via CLI/pipeline after deploy — Bicep provisions the slots, the pipeline swaps them.)

@description('Location for all resources')
param location string = resourceGroup().location

@description('Globally-unique web app name')
param appName string

var planName = 'plan-slots-lab'

resource plan 'Microsoft.Web/serverfarms@2023-12-01' = {
  name: planName
  location: location
  sku: { name: 'S1', tier: 'Standard' }   // Standard → 5 slots + swap warm-up
  kind: 'linux'
  properties: { reserved: true }           // reserved=true means Linux
}

resource site 'Microsoft.Web/sites@2023-12-01' = {
  name: appName
  location: location
  properties: {
    serverFarmId: plan.id
    httpsOnly: true
    siteConfig: {
      linuxFxVersion: 'DOTNETCORE|8.0'
      alwaysOn: true
      appSettings: [
        { name: 'ENV_NAME', value: 'production' }   // marked sticky below
        { name: 'RELEASE',  value: 'v1.0.0' }       // travels on swap
      ]
    }
  }
}

// Mark ENV_NAME as a deployment-slot (sticky) setting on production
resource prodSlotConfig 'Microsoft.Web/sites/config@2023-12-01' = {
  parent: site
  name: 'slotConfigNames'
  properties: {
    appSettingNames: [ 'ENV_NAME' ]   // sticky app settings (do not swap)
    // connectionStringNames: [ 'DB_CONNECTION' ]  // sticky connection strings go here
  }
}

// The staging slot
resource staging 'Microsoft.Web/sites/slots@2023-12-01' = {
  parent: site
  name: 'staging'
  location: location
  properties: {
    serverFarmId: plan.id
    httpsOnly: true
    siteConfig: {
      linuxFxVersion: 'DOTNETCORE|8.0'
      alwaysOn: true
      appSettings: [
        { name: 'ENV_NAME', value: 'staging' }                       // sticky (per slotConfigNames)
        { name: 'RELEASE',  value: 'v1.1.0' }                        // travels on swap
        { name: 'WEBSITE_SWAP_WARMUP_PING_PATH', value: '/' }        // warm-up gate
        { name: 'WEBSITE_SWAP_WARMUP_PING_STATUSES', value: '200' }
      ]
    }
  }
}

output productionHost string = site.properties.defaultHostName
output stagingHost string = staging.properties.defaultHostName

Deploy and then swap from the pipeline:

# Deploy the infrastructure (provisions plan + app + staging slot)
az deployment group create -g $RG \
  --template-file slots.bicep \
  --parameters appName=$APP -o table

# Swap is an operation, run after deploy
az webapp deployment slot swap -n $APP -g $RG \
  --slot staging --target-slot production --action swap -o table

Validate: az deployment group show -g $RG -n slots --query properties.provisioningState -o tsv returns Succeeded, and the slotConfigNames resource confirms ENV_NAME is sticky:

az webapp config appsettings list -n $APP -g $RG \
  --query "[?name=='ENV_NAME'].slotSetting" -o tsv   # → true

Part F — The portal track (same workflow, click-by-click)

If you prefer the portal (or need to show a teammate), the identical flow:

  1. Create the slot. Web app → Deployment slotsAdd Slot → Name staging, Clone settings from your production app → Add. The slot appears with its own URL.
  2. Differentiate settings. Slot stagingSettings → Environment variables (Configuration) → set ENV_NAME=staging and tick Deployment slot setting (sticky); set RELEASE=v1.1.0 and leave the box unticked (travels). Apply (this restarts the slot, not production).
  3. Add warm-up. Same blade → add WEBSITE_SWAP_WARMUP_PING_PATH=/ and WEBSITE_SWAP_WARMUP_PING_STATUSES=200, mark them Deployment slot setting. Apply.
  4. Swap with preview. Web app → Deployment slotsSwap → Source staging, Target production, toggle Perform swap with previewStart Swap. The dialog shows the config changes that will apply — read them to confirm sticky settings stay put. Smoke-test the staging URL, then Complete Swap (or Cancel Swap).
  5. Roll back. Deployment slots → Swap again with the same source/target to reverse it.
  6. Canary. Deployment slots → set a Traffic % on the staging row (e.g. 20) → Save; clear it by setting back to 0.

The portal’s Swap dialog showing “Config changes” is the GUI equivalent of --action preview — it’s the single best place to catch a sticky-vs-travelling mistake before it goes live.

Teardown

Delete everything to stop plan charges:

az group delete -n $RG --yes --no-wait

Cost note: an S1 plan is a few rupees per hour; an hour of this lab is well under ₹60, and deleting the resource group stops the plan, the app, and the slot together. (S1 is the cheapest tier with the full 5-slot + warm-up feature; Basic has only limited slot support.)

Common mistakes & troubleshooting

The failure modes that actually happen, with the exact way to confirm and fix each. First the scannable table, then the detail for the ones that bite hardest.

# Symptom Root cause Confirm (exact cmd / portal path) Fix
1 After a swap, production talks to the staging database/cache Environment connection string not sticky — it travelled with the content appsettings list --query "[?name=='DB_CONNECTION'].slotSetting"false/null Mark it --slot-settings on both slots
2 A swap hangs then fails/aborts, production unchanged Warm-up path returns a non-accepted status or times out curl -i the warm-up path on the staging URL; slot logs show it failing Fast healthy path; widen WEBSITE_SWAP_WARMUP_PING_STATUSES only if legit
3 Brief cold 502 / slow first request right after swap Swap with no warm-up path (or a trivial one) The slot’s WEBSITE_SWAP_WARMUP_PING_PATH is empty Add a warm-up path that primes config + required backend
4 A feature flag didn’t promote after swap Flag marked sticky by mistake, so it stayed on the slot slotSetting on the flag → true Un-stick it: set as a normal --settings value
5 “Cannot add slot — slot limit reached” Basic (limited) or at the tier cap (S=5, P/I=20) az appservice plan show --query sku.tier; count slots Delete unused slots or scale to S1+
6 Settings/changes went to the wrong slot Forgot --slot staging, so they hit production appsettings list (no --slot) shows the value on prod Re-apply with the correct --slot; remove the stray
7 Swap “succeeds” but the new build isn’t live You deployed to production, not the slot Slot’s deployment history vs production Deploy to --slot staging, then swap
8 Auto-swap configured but never fires Unsupported for the stack (Linux container) or misconfigured az webapp deployment slot auto-swap show Use a pipeline swap step instead
9 After swap, Key Vault refs break in prod A KV ref pointed at the staging vault and travelled Environment variables blade red error; az webapp identity show Make env-specific KV refs sticky; scope each slot’s identity
10 Staging slot makes production slow Busy staging load competes for the shared plan’s CPU/RAM Plan CpuPercentage/MemoryPercentage high while only staging loaded Don’t load-test the prod plan’s slot; separate plan
11 Long-running requests dropped at swap time In-flight connections cut when routing switches Failed-request spike exactly at the swap timestamp Keep requests short; swap in low-traffic windows for long ops
12 Swap reverted on its own Warm-up failed the gate → platform auto-rolled-back Activity log shows the swap then a revert Fix the build/warm-up path; re-run once warm-up is green

Three with non-obvious nuance beyond the table. Production hit the staging backend (row 1) because a travelling connection string fails silently — production reads/writes the staging database until the data looks wrong; in Bicep the fix is adding the name to slotConfigNames. The swap hung then aborted (row 2) is the feature working — warm-up couldn’t get an accepted status, so it stopped a bad build going live; reproduce with curl -i https://<app>-staging.azurewebsites.net/warmup and don’t “fix” it by removing warm-up. Key Vault refs broke after a swap (row 9) because managed identities are slot-specific and don’t swap, but a Key Vault reference value pointing at the staging vault does travel unless sticky — so the symptom is a travelled reference, not a missing identity.

Best practices

Security notes

The security controls that also keep releases safe — secure and resilient pull the same way here:

Control Setting / mechanism Secures against Also prevents
Sticky env secrets / KV refs --slot-settings / slotConfigNames Prod secrets leaking to staging on swap Swapping the wrong backend into prod
Per-slot least-privilege identity Slot managed identity + scoped RBAC A slot reaching another env’s secrets KV-reference break after swap
Staging access restrictions ipSecurityRestrictions per slot Public access to pre-release code Bots/crawlers hitting unfinished features
Per-slot SCM lockdown scmIpSecurityRestrictions Unauthorised deploys to a slot Tampered builds entering the swap pipeline
HTTPS-only per slot httpsOnly, minTlsVersion Cleartext / downgrade on staging “Temporary” TLS-off on a forgotten slot

Cost & sizing

The headline: slots add no line item to the App Service bill — they run on the plan you already pay for. A staging slot on a 2× S1 plan costs the same as that plan with no slot. What you pay for is the plan SKU and instance count, sized for production load; the slot rides along on those instances, and reverse swaps and swaps-with-preview are free operations on existing instances.

Two things that do move the bill:

A rough monthly picture for a small production API with slots:

Setup What you pay for Rough INR / month Slots included Notes
1× S1 + staging slot One Standard instance ~₹6,000–7,000 Up to 5 Slot is free; one instance = swap less smooth
2× S1 + staging slot Two Standard instances (HA) ~₹12,000–14,000 Up to 5 Recommended baseline; smooth swaps
2× P1v3 + slots Two Premium v3 (8 GB, pre-warmed) ~₹28,000–36,000 Up to 20 When you need RAM / pre-warm / more slots
Application Insights Per-GB telemetry (per env) ~₹1,000–3,000 n/a Watch error rate across swaps; sample high traffic

The sizing rule: choose the smallest tier that includes the slots you actually use (almost always Standard for production+staging), size instance count for production load and smooth swaps (two is a sensible floor), and treat the staging slot as free as long as it’s idle between deploys — because the moment you run sustained load on it, it’s borrowing from production and you’ve under-sized the plan.

Interview & exam questions

1. What is an App Service deployment slot, and how is it different from a separate environment? A slot is a live copy of the web app running on the same App Service plan, with its own hostname, configuration, and deployment. Unlike a separate environment (a different plan/app), a slot shares the production plan’s instances, which is what makes a swap near-zero-downtime — staging instances are already warm on the same plan.

2. Explain exactly what happens during a swap. App Service applies the target slot’s sticky settings to the source slot’s instances, warms them up (pinging the warm-up path against production config), and only then switches the internal routing so the warmed instances answer the production hostname. The previous production version lands in the staging slot. No worker boots from cold during the cutover, so a warmed swap is near-zero-downtime.

3. Which app settings move during a swap and which stay? By default, settings and connection strings travel with the content (so staging’s value reaches production). Settings marked as a deployment slot setting (sticky) stay bound to their slot and never swap. You make environment-defining config (DB, cache, secrets, slot identity) sticky, and let build-coupled config (feature flags) travel.

4. A swap left production talking to the staging database. What went wrong and how do you fix it? The connection string was a normal app setting, not sticky (slotSetting:false), so it travelled with staging’s content into production. Set it on both slots with --slot-settings so each pins its own backend, then swap again to correct production.

5. What is swap warm-up and what does it protect against? The platform pings WEBSITE_SWAP_WARMUP_PING_PATH on the incoming instances (configured with production’s sticky settings) and completes the swap only if it returns a status in WEBSITE_SWAP_WARMUP_PING_STATUSES. If it can’t, the swap aborts and rolls back — protecting production from a build that can’t serve and from post-cutover cold-start blips.

6. What is a swap with preview (multi-phase swap) and when do you use it? Phase 1 applies the target (production) config to the source (staging) slot without switching routing, so you can smoke-test the new build with production configuration on the staging URL; phase 2 completes (or you reset with no impact). Use it for risky releases where you want to validate against prod config before committing.

7. How do you roll back a bad release deployed via slots? Run the swap in reverse — the previous production version is sitting in the staging slot after the original swap, so one reverse swap restores it in seconds, with no rebuild or redeploy. This is the single biggest operational win of slots.

8. What tier do you need for deployment slots, and how many do you get? Free/Shared have none. Basic has limited slot support. Standard (S1–S3) gives up to 5 slots (including production). Premium v3 (P1v3–P3v3) and Isolated give up to 20. Slots add no separate cost — they run on the plan you already pay for.

9. What is traffic splitting and how does it relate to a swap? It routes a percentage of production traffic to a slot (--distribution staging=10) for canary testing, pinning each user to one version per session via the x-ms-routing-name cookie. It is not a swap (production stays as-is); the flow is split → watch telemetry → ramp → swap to promote.

10. What is auto-swap and when should you avoid it? Auto-swap makes a slot automatically swap into a target after every successful deployment to it — hands-off staging→prod promotion. Avoid it when you want a human gate or a manual smoke test (a bad build that passes warm-up still goes live automatically), and note it isn’t supported the same way on Linux/custom-container apps.

11. Why can slots make production slower, and how do you prevent it? Slots share the plan’s CPU/RAM, so a busy staging slot (e.g. a load test) steals resources from production on the same instances. Don’t load-test the production plan’s slot — use a separate plan — and size instance count for combined demand.

12. Slots swap code, not schema — what does that imply for database changes? Because both the old and new build may run against the same database across the swap window (and rollback puts the old build back), schema changes must be backward-compatible (expand/contract) — add columns before using them, don’t drop/rename in the same release. Otherwise a swap or rollback breaks one of the two builds.

These map to AZ-204 (Developer Associate)implement deployment slots; configure and swap slots; CI/CD to App Service — and AZ-104 (Administrator)configure App Service deployment slots, scaling, and app settings. A compact cert-mapping for revision:

Question theme Primary cert Exam objective area
What a slot is; swap mechanics AZ-204 Implement App Service; deployment slots
Sticky vs travelling settings AZ-204 / AZ-104 Configure app settings; slots
Warm-up, preview, rollback AZ-204 Deploy code; manage releases
Traffic splitting / canary AZ-204 Implement deployment strategies
Tiers & slot limits AZ-104 Configure & manage App Service plans
Schema-safe deploys with slots AZ-204 CI/CD; release management

Quick check

  1. During a swap, do app settings travel with the content or stay on the slot by default — and how do you change a given setting’s behaviour?
  2. You marked your DB_CONNECTION correctly, but after a swap a feature flag you shipped didn’t take effect. What did you most likely do wrong?
  3. What does swap with preview let you validate that a direct swap does not?
  4. Name the two app settings that configure swap warm-up, and say what happens if the warm-up path keeps returning a 500.
  5. You deployed a bad build via a swap and production is broken. What’s the fastest way back, and roughly how long does it take?

Answers

  1. By default settings travel with the content (staging’s value reaches production on swap). To pin a setting to its slot, mark it as a deployment slot setting (sticky)--slot-settings in the CLI, the “Deployment slot setting” checkbox in the portal, or slotConfigNames in Bicep. Sticky settings never move during a swap.
  2. You marked the feature flag sticky, so it stayed bound to the slot instead of travelling with the build. Feature flags shipped with the code should be normal (travelling) settings; reserve sticky for environment-defining config (DB, cache, secrets, slot identity).
  3. Swap with preview applies production’s configuration to the staging slot without switching routing, so you can smoke-test the new build against production config on the staging URL before committing. A direct swap just runs warm-up and cuts over — you don’t get to interactively validate the prod-configured build first.
  4. WEBSITE_SWAP_WARMUP_PING_PATH (the path pinged) and WEBSITE_SWAP_WARMUP_PING_STATUSES (the accepted statuses). If the path keeps returning 500, the warm-up health gate fails and the swap aborts and rolls back — production is never cut over to the broken build.
  5. A reverse swap: the previous production version is sitting in the staging slot after the original swap, so swapping back restores it in seconds (no rebuild, no redeploy). This is why slots make rollback trivial.

Glossary

Next steps

You can now create slots, mark settings sticky, warm up a swap, roll back, and canary — the full release workflow. Build outward:

AzureApp ServiceDeployment SlotsSwapBlue-GreenCI/CDZero DowntimeWeb Apps
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

Keep Reading