The queue has 40,000 messages and the container app is still running one replica. Or it runs zero replicas and the first request after a quiet period times out. In Container Apps, scaling is handled by KEDA, driven by the scale block of the active revision. When that block is wrong, or KEDA can't authenticate to the event source, nothing scales and nothing obvious fails.
This guide shows how to read the scale config, set up working HTTP and Service Bus rules, and confirm KEDA is acting on them.
Symptom to cause
| Symptom | Likely cause | Fix |
|---|---|---|
| Replicas never go above 1 | maxReplicas is 1, or the rule is on an inactive revision | Raise --max-replicas, check the active revision |
| Queue grows, replicas stay at 0 | Scaler can't authenticate to Service Bus or Storage | Fix --scale-rule-auth secret or --scale-rule-identity and the role |
| Rule exists but does nothing | Wrong metadata key (queueName, namespace) or wrong queue name | Correct the metadata; names are case-sensitive |
| HTTP app never scales out | Ingress disabled, or concurrency threshold set too high | Enable ingress, lower --scale-rule-http-concurrency |
| Cold start timeouts | minReplicas is 0 on a latency-sensitive app | Set --min-replicas 1 or more |
| Replicas flap up and down | Threshold close to normal load, short cooldown | Raise the threshold or the cooldown |
| CPU/memory rule doesn't scale to zero | Expected: CPU and memory rules can't scale to zero | Use an event or HTTP rule if you need zero |
Step 1: read what is actually configured
Scale settings belong to the revision template. Check the active revision and its scale block:
az containerapp show -n ca-orders -g rg-apps \
--query "{revisionMode:properties.configuration.activeRevisionsMode, latest:properties.latestRevisionName, ingress:properties.configuration.ingress.external, scale:properties.template.scale}" -o json
az containerapp revision list -n ca-orders -g rg-apps \
--query "[].{name:name, active:properties.active, traffic:properties.trafficWeight, replicas:properties.replicas, health:properties.healthState}" -o table
In multiple revision mode, an older revision can still be taking traffic with an older scale config. Fix the scale rule, then move traffic to the new revision or deactivate the old one.
See the replicas running right now:
az containerapp replica list -n ca-orders -g rg-apps -o table
Step 2: set min and max replicas deliberately
az containerapp update -n ca-orders -g rg-apps \
--min-replicas 1 --max-replicas 20
minReplicas 0saves money but adds a cold start to the first request or message after idle. Use it for background workers, not for user-facing APIs.maxReplicasalso caps how hard you can hit a downstream database. Set it to what the database and its connection limit can take, not to the highest value allowed. Check the current per-app and per-environment limits in the docs; they differ by plan.
Warning
Changing anything under
template, including scale settings, creates a new revision. In single revision mode, traffic moves to the new revision once it is healthy. Make the change in a quiet period or test it on a non-production app first.
Step 3: HTTP scaling
HTTP rules scale on concurrent requests per replica. Ingress must be enabled, because the platform counts requests at the ingress layer.
az containerapp update -n ca-api -g rg-apps \
--min-replicas 2 --max-replicas 30 \
--scale-rule-name http-concurrency \
--scale-rule-type http \
--scale-rule-http-concurrency 50
Pick the concurrency from a load test: the number of in-flight requests one replica handles before latency climbs. The default is low (check the current default for your API version), which is fine for slow handlers and too aggressive for fast ones.
Step 4: Service Bus scaling with managed identity
Event-driven rules use KEDA scalers. For Service Bus, KEDA reads the queue's message count and adds replicas when it goes over messageCount per replica.
Use a managed identity rather than a connection string. Assign a user-assigned identity to the app, give it a role on the namespace that can read queue metrics, and pass it to the rule:
IDENTITY_ID=$(az identity show -n id-ca-orders -g rg-apps --query id -o tsv)
PRINCIPAL_ID=$(az identity show -n id-ca-orders -g rg-apps --query principalId -o tsv)
SB_ID=$(az servicebus namespace show -n sb-prod-orders -g rg-msg --query id -o tsv)
az containerapp identity assign -n ca-orders -g rg-apps --user-assigned $IDENTITY_ID
az role assignment create --assignee-object-id $PRINCIPAL_ID \
--assignee-principal-type ServicePrincipal \
--role "Azure Service Bus Data Owner" --scope $SB_ID
az containerapp update -n ca-orders -g rg-apps \
--min-replicas 0 --max-replicas 20 \
--scale-rule-name sb-orders \
--scale-rule-type azure-servicebus \
--scale-rule-metadata "queueName=orders" "namespace=sb-prod-orders" "messageCount=20" \
--scale-rule-identity $IDENTITY_ID
Microsoft's examples use Azure Service Bus Data Owner because the scaler needs to read entity runtime properties. Data Receiver alone is often not enough for the scaler, even though it's enough for the app to receive messages. If your security team pushes back, test a narrower role in non-production first.
--scale-rule-identity also accepts system for the system-assigned identity. Managed identity for scale rules is a newer feature: if your CLI doesn't recognise the flag, run az extension update --name containerapp.
Connection-string fallback
If you must use a secret, store it on the app and map it to the scaler's connection parameter:
az containerapp secret set -n ca-orders -g rg-apps \
--secrets "sb-conn=<namespace-connection-string>"
az containerapp update -n ca-orders -g rg-apps \
--scale-rule-name sb-orders \
--scale-rule-type azure-servicebus \
--scale-rule-metadata "queueName=orders" "messageCount=20" \
--scale-rule-auth "connection=sb-conn"
Caution
When the connection string's key is rotated, the scaler stops working silently: replicas stay at the minimum while the queue backs up. If you rotate keys, update the secret in the same change, or move to managed identity.
Other scalers
The same pattern works for Storage queues (--scale-rule-type azure-queue, metadata queueName, queueLength, accountName) and Event Hubs (azure-eventhub). Metadata keys follow the KEDA scaler documentation for that scaler type. A misspelled key is ignored, not rejected, so copy the key names exactly.
Step 5: confirm KEDA is acting on the rule
System logs show scaler errors and scale decisions. From the CLI:
az containerapp logs show -n ca-orders -g rg-apps --type system --tail 100
In Log Analytics:
ContainerAppSystemLogs_CL
| where ContainerAppName_s == "ca-orders"
| where TimeGenerated > ago(1h)
| where Log_s has_any ("scale", "KEDA", "scaler", "unauthorized", "failed")
| project TimeGenerated, RevisionName_s, Reason_s, Log_s
| order by TimeGenerated desc
If the environment sends logs through diagnostic settings, the table is ContainerAppSystemLogs and the columns have no _s suffix (ContainerAppName, Log, Reason). Check which one your workspace has.
Watch the replica count against the queue depth to confirm the rule works:
APP_ID=$(az containerapp show -n ca-orders -g rg-apps --query id -o tsv)
az monitor metrics list --resource $APP_ID --metric Replicas \
--interval PT1M --aggregation Maximum --offset 1h -o table
az servicebus queue show -g rg-msg --namespace-name sb-prod-orders -n orders \
--query "countDetails.activeMessageCount"
Tuning notes
- Threshold maths. With
messageCount=20and 400 active messages, KEDA targets about 20 replicas, capped bymaxReplicas. If one replica clears 200 messages a minute, a threshold of 20 is much too low and you'll overshoot. - Polling and cooldown. KEDA checks scalers on a polling interval and waits a cooldown period before scaling back to zero. Newer API versions expose
pollingIntervalandcooldownPeriodon the scale block; check the defaults and whether your API version supports them before relying on them. - Graceful shutdown. Scale-in sends SIGTERM to replicas. Workers must stop pulling messages and finish or abandon in-flight ones within the termination grace period, or you'll see lock-lost retries and duplicates.
- Multiple rules. If you set several rules, the app scales to the highest replica count any rule asks for.
Prevent it next time
- Keep scale rules in Bicep or YAML with the app, not as portal edits, so a redeploy doesn't wipe them.
- Use managed identity for scaler auth; it removes the key-rotation failure mode.
- Alert when queue depth stays above a threshold for 10 minutes while
Replicasis at its minimum. That combination means the scaler is broken. - Load test once to set HTTP concurrency and
messageCountfrom measured per-replica throughput. - Review
maxReplicasagainst downstream limits whenever the database tier or connection pool changes.