You pushed a new image, the deployment "succeeded", and the app is down. The new revision shows Provisioning for ten minutes, then Degraded with Deployment Progress Deadline Exceeded. 0/1 replicas ready. Or the revision is Running but every request returns 404 or times out. This guide covers how to get traffic back to a working revision first, then find which of the four usual causes broke the new one: image pull, a crashing container, failing health probes, or ingress settings.
First: get traffic back to a working revision
Find the last revision that worked before you start debugging.
APP=ca-orders-api; RG=rg-orders-prod
az extension add --name containerapp --upgrade
az containerapp revision list -n $APP -g $RG --all \
--query "[].{name:name, created:properties.createdTime, active:properties.active, traffic:properties.trafficWeight, health:properties.healthState, running:properties.runningState, prov:properties.provisioningState}" \
-o table
az containerapp show -n $APP -g $RG \
--query "{mode:properties.configuration.activeRevisionsMode, latest:properties.latestRevisionName, latestReady:properties.latestReadyRevisionName}"
If latestRevisionName and latestReadyRevisionName are different, the new revision never became ready. The difference between multiple and single revision mode matters here:
Multiple revision mode: activate the old revision and send all traffic to it.
az containerapp revision activate -n $APP -g $RG --revision ca-orders-api--v41 az containerapp ingress traffic set -n $APP -g $RG --revision-weight ca-orders-api--v41=100Single revision mode: the platform normally keeps the old revision serving until the new one is ready. If it doesn't, deploy the previous image tag again:
az containerapp update -n $APP -g $RG --image acrorders.azurecr.io/orders-api:1.41.0
Warning
Redeploying with a mutable tag such as
latestcan pull the broken image again. Roll back to an immutable tag or digest (orders-api@sha256:...).
Where to look
| Symptom | Most likely cause | Go to |
|---|---|---|
Deployment fails right away, or revision is Failed with an image error | Registry auth, network path to the registry, or wrong tag | Image pull |
Revision Degraded, replicas restart repeatedly | App exits on start: missing env var or secret, bad config, out of memory | Crash loop |
Provisioning/Processing for minutes, then Degraded with "0/1 replicas ready" | Probes fail: wrong port, path, or the app starts too slowly | Health probes |
Revision Running and Healthy, requests fail with 404, 403 or timeout | Ingress type, target port, IP restrictions, internal environment | Ingress |
| Revision healthy, but responses come from old code | Traffic still split to an older revision, or the tag was reused | Traffic split |
Read the logs for the bad revision
System logs show what the platform did (pulls, probe results, restarts). Console logs show your container's stdout/stderr.
REV=ca-orders-api--v42
az containerapp logs show -n $APP -g $RG --revision $REV --type system --tail 100 --format text
az containerapp replica list -n $APP -g $RG --revision $REV -o table
az containerapp logs show -n $APP -g $RG --revision $REV \
--replica <replica-name> --container orders-api --type console --tail 100 --format text
If no replica exists (the revision scaled to zero or never started), use Log Analytics. With the environment's logs sent to Log Analytics directly, the tables are ContainerAppSystemLogs_CL and ContainerAppConsoleLogs_CL. With diagnostic settings, they are ContainerAppSystemLogs and ContainerAppConsoleLogs, and the column names don't have the _s suffix. Check which one your environment uses.
ContainerAppSystemLogs_CL
| where TimeGenerated > ago(1h)
| where ContainerAppName_s == "ca-orders-api" and RevisionName_s == "ca-orders-api--v42"
| project TimeGenerated, Reason_s, Log_s
| order by TimeGenerated desc
ContainerAppConsoleLogs_CL
| where TimeGenerated > ago(1h)
| where RevisionName_s == "ca-orders-api--v42"
| where Log_s has_any ("error", "exception", "fatal", "denied")
| project TimeGenerated, ContainerName_s, Log_s
Cause 1: image pull fails
Look for pull, unauthorized or manifest errors in the system log.
# Which registry credentials does the app use?
az containerapp registry list -n $APP -g $RG -o table
# Does the tag exist?
az acr repository show-tags -n acrorders --repository orders-api --orderby time_desc --top 5 -o table
If you use a managed identity, check that the identity has AcrPull on the registry:
PRINCIPAL=$(az containerapp show -n $APP -g $RG --query identity.principalId -o tsv)
ACR_ID=$(az acr show -n acrorders --query id -o tsv)
az role assignment list --assignee $PRINCIPAL --scope $ACR_ID -o table
# Fix: grant the role, then point the app at the registry with that identity
az role assignment create --assignee $PRINCIPAL --role AcrPull --scope $ACR_ID
az containerapp registry set -n $APP -g $RG --server acrorders.azurecr.io --identity system
In a VNet-integrated environment, also check:
- Does a firewall or route table (UDR) block egress to the registry?
- Does custom DNS resolve
acrorders.azurecr.io(and its private endpoint, if it has one)? - Does the custom DNS forward unresolved queries to
168.63.129.16?
Cause 2: the container crashes on start
Console logs usually show the reason, such as a stack trace, KeyError, a connection refused to a database, or nothing at all, which often means an out-of-memory kill. Compare the new revision's template with the last good one:
az containerapp revision show -n $APP -g $RG --revision ca-orders-api--v41 --query properties.template > good.json
az containerapp revision show -n $APP -g $RG --revision $REV --query properties.template > bad.json
diff good.json bad.json
Common findings:
- A new environment variable references a secret that isn't defined on the app.
- A Key Vault secret reference the identity can't read.
- CPU/memory lowered below what the runtime needs.
- A changed command or args.
To check the environment from inside a running replica:
az containerapp exec -n $APP -g $RG --revision ca-orders-api--v41 --command "sh"
Cause 3: health probes fail
If you define no probes and ingress is enabled, Container Apps adds TCP startup and readiness probes on the ingress target port. If the app listens on a different port, or takes longer to start than the probes allow, the revision never becomes ready.
az containerapp ingress show -n $APP -g $RG --query "{targetPort:targetPort, external:external, transport:transport}"
az containerapp show -n $APP -g $RG --query "properties.template.containers[0].probes"
Probe settings aren't exposed as az containerapp update flags. Export the app to YAML, edit the probes, and apply the file:
az containerapp show -n $APP -g $RG -o yaml > app.yaml
# edit template.containers[0].probes: port, path, initialDelaySeconds, failureThreshold
az containerapp update -n $APP -g $RG --yaml app.yaml
probes:
- type: Startup
httpGet: { path: /healthz, port: 8080 }
periodSeconds: 5
failureThreshold: 30 # allows ~150 s to start
- type: Readiness
httpGet: { path: /ready, port: 8080 }
periodSeconds: 10
Tip
Don't make the readiness probe check downstream dependencies such as the database. If the database has a problem, every replica goes unready together and you lose the whole app instead of degrading.
Cause 4: revision healthy, requests still fail
| Response | Check |
|---|---|
404 Container App is stopped or does not exist from inside the VNet | On an internal environment, ingress set to "Limited to Container Apps Environment". Set --type external (on an internal environment this doesn't make it public) |
| 403 | IP security restrictions, or client certificate mode set to Require |
| Timeout / connection reset | Target port doesn't match the port the app listens on, HTTP vs TCP transport, NSG or firewall in front of the environment |
| Old responses | Traffic weights in multiple mode, or a reused image tag |
az containerapp ingress update -n $APP -g $RG --target-port 8080
az containerapp ingress access-restriction list -n $APP -g $RG -o table
az containerapp ingress traffic show -n $APP -g $RG -o table
Prevent it next time
- Deploy immutable tags or digests. Never deploy
latestto production. - Use multiple revision mode for production apps. Deploy the new revision with 0% traffic, check it through its revision URL or a label, then shift traffic.
- Define explicit startup and readiness probes on the real port, with a startup budget that matches your slowest observed start.
- Alert on system logs for the app (pull failures, probe failures, restarts) instead of waiting for users to report it.
- Use a managed identity with
AcrPullfor registry access. Don't use admin credentials that can be rotated out from under you.