3.1.3. Deploying Containers to App Service
💡 First Principle: App Service treats your container as a black box with two contracts: it must listen on a declared port, and it reads configuration from environment variables the platform injects at startup. Honor both contracts and the image from 3.1.1 runs unmodified across dev, staging, and production — configuration varies, the artifact never does.
App Service (Web App for Containers) is the lowest-friction hosting from the 1.2.1 spectrum: bring an image, get TLS, scaling, and deployment slots. Creation points the app at an ACR image, with managed-identity pull (AcrPull) as best practice — no registry passwords in app config.
The configuration contract is where exam questions live. App settings become environment variables inside the container at runtime, and they override any same-named ENV from the Dockerfile:
az webapp config appsettings set --name rag-api --resource-group rg \
--settings MODEL_ENDPOINT="https://..." COSMOS_DB="chatdb" \
WEBSITES_PORT=8000 \
OPENAI_KEY="@Microsoft.KeyVault(SecretUri=https://kv.vault.azure.net/secrets/openai-key/)"
Three details deserve flagging. First, WEBSITES_PORT tells App Service which port your container listens on — the setting behind most "container deploys but never responds" mysteries (App Service assumes 80; your FastAPI app listens on 8000). Second, the @Microsoft.KeyVault(...) syntax is a Key Vault reference: the app setting holds a pointer, App Service's managed identity fetches the secret at startup, and code just reads an ordinary environment variable — secrets absent from both image and configuration, previewing 5.1.1. Third, changing app settings restarts the container — configuration is applied at startup, not live-patched.
This mechanism is precisely why the parent misconception is wrong: one image, promoted untouched through environments, each environment supplying its own variables — the container equivalent of the "build once, deploy many" doctrine.
⚠️ Exam Trap: A custom container that starts, logs nothing wrong, but times out on every request is missing WEBSITES_PORT. Distractors will offer scaling up, enabling continuous deployment, or changing the image — the port declaration is the fix.
Slots deserve a mention here too: deployment slots let a new container version warm up on a staging slot and swap into production atomically, with app settings optionally staying slot-sticky.
Reflection Question: Why does resolving secrets via Key Vault references beat passing them as plain app settings, given both end up as environment variables in the container?