I load-tested one container app twice with the same HTTP scale rule. Forty slow requests at a time got six replicas. Ten fast requests at a time got ten, the maximum. More requests waiting, fewer replicas. And when the traffic stopped, the last replica stayed for almost ten minutes.
This explainer shows what the HTTP scale rule in Azure Container Apps really counts, why replicas stay after the traffic is gone, and what scale to zero costs the first user. Everything below comes from a real run in my own Azure subscription.
Key points
- Container Apps only scales between your minimum and maximum. The Microsoft Learn exercise starts the app with min 1 and max 1: forty users waited, nothing errored, and the replica count never moved.
- The HTTP rule counts requests per second, not requests waiting. Every 15 seconds Container Apps takes the requests of the last 15 seconds and divides by 15. Fast requests produce more requests per second, so they get more replicas.
- Replicas leave slowly. Extra replicas go after a 5-minute window; the last one has a cool down of its own (5 minutes by default). In my run, zero came 9 minutes 47 seconds after the last request.
- Zero costs nothing, except the first user's wait. The first request after zero took 29.6 seconds. With a minimum of one it took 0.16 seconds, and an idle replica is billed at a lower rate.
One replica
A replica is one running copy of your container, and scaling out means more copies. Without a scale rule of your own, Container Apps uses a default HTTP rule between zero and ten replicas. The lab sets both limits to one, so there is no room to scale.
With forty users at once and two seconds of work per request, there was no error at all. The app's web server (gunicorn with 2 workers × 8 threads) handles sixteen requests at a time; the other twenty-four waited in line. Two seconds of work took a median of 4.15 seconds, and the replica count stayed at one for the whole 90 seconds.
The lab's HTTP rule:
az containerapp update -n agent-api -g rg-demo \--min-replicas 0 --max-replicas 10 \--scale-rule-name http-scaling \--scale-rule-type http \--scale-rule-http-concurrency 10
The same in the template:
scale:minReplicas: 0maxReplicas: 10rules:- name: http-scalinghttp:metadata:concurrentRequests: "10"
Scale settings live in the revision's template, so this change builds a new revision (here agent-api--v2). A revision is a snapshot of your app's image and settings.
What the rule counts
The setting is called concurrentRequests, so it reads like "requests in flight, ten per replica". By that reading, forty slow clients need four replicas and ten fast clients need one. The run says otherwise:
| Test | Clients | Work per request | Requests per second | Replicas |
|---|---|---|---|---|
| Slow | 40 | 2 s | about 20 | 6 |
| Fast | 10 | 50 ms | about 120 | 10 (the maximum) |
The docs define the number as a rate: every 15 seconds, the requests of the last 15 seconds divided by 15. It counts customers coming through the door, not the line at the till. The fast clients came back quickly and sent about 120 requests per second: divided by the target of 10, that is 12 replicas, capped at the maximum of 10.
The slow clients sent only about 20 per second, a sixth of the fast ones, so they got fewer replicas. By the formula alone, 20 divided by 10 is two. I ran the slow test again for nine minutes, and it held six. The docs call replica counts a target, not a guarantee. The formula tells you the direction; load test with your real request times and watch the replica count.
How the scale-out moves, from the docs: desiredReplicas = ceil(current metric ÷ target), scale-up steps of 1, 4, 8, 16 … up to the maximum, no wait before a scale-up. In my slow test the count went 1 → 4 within 11 seconds, then 4 → 6.
When the traffic stops
| Phase | Docs default | My run (slow test) |
|---|---|---|
| Extra replicas leave | after a 300-second scale-down window | 6 → 1 at 329–345 s after the last request |
| The last replica → zero | after the 300-second cool down | at 587 s (9 min 47 s) |
| First request after zero | a cold start: new replica, image pull, app start | 29.6 s, then 0.14 s |
While you wait for zero, every replica above the minimum is billed at the active rate. At zero replicas there are no usage charges.
Two surprises
A deployment during a peak. Right after the fast test I changed only the minimum (min 1, revision v3). The new revision started with ten replicas, as many as the old one had, and kept them for five more minutes. That is the zero-downtime switch: a new revision has to carry the current load before it gets the traffic. Don't read those replicas as a scaling bug.
No ingress and a minimum of zero. Without ingress there are no HTTP requests to count. If you also define no minimum and no other scale rule, the app scales to zero and has no way of starting again. Set a minimum of one, or scale on something else, like the length of a queue.
What happened when I ran it
Tested on October 20, 2026 in my own Azure subscription (westeurope, Azure CLI 2.88.0); everything was deleted afterwards. The app is the mock agent API from the Microsoft Learn exercise (Flask, POST /agent/process waits a given number of milliseconds); watch.py, my own script, sent the load from keep-alive clients and polled az containerapp replica list every few seconds. My first attempt in germanywestcentral failed at the environment, the same AKSCapacityHeavyUsage error as the day before. In westeurope the environment took 15.7 minutes, the cloud build 68 seconds, the app 41 seconds.
The lab's start, min 1 and max 1:
> az containerapp show -n agent-api -g rg-demo --query properties.template.scale -o json{"cooldownPeriod": 300,"maxReplicas": 1,"minReplicas": 1,"pollingInterval": 30,"rules": null}
The fast test (10 clients × 50 ms), the first requests waiting for a cold start:
34 s replicas 1 | 48.7 req/s (last 15 s) 10 in flight median 0.08 s42 s replicas 10 | 112.8 req/s (last 15 s) 10 in flight median 0.08 s50 s replicas 10 | 120.3 req/s (last 15 s) 10 in flight median 0.08 ssummary: 11973 requests ok, 0 failed, median 0.08 s, p95 0.09 s, peak 10 replicas at 42 s
The slow test, nine minutes long:
16 s replicas 4 | 7.5 req/s (last 15 s) 40 in flight median 4.13 s56 s replicas 6 | 20.9 req/s (last 15 s) 40 in flight median 2.03 s302 s replicas 6 | 18.7 req/s (last 15 s) 40 in flight median 2.03 s535 s replicas 6 | 20.9 req/s (last 15 s) 40 in flight median 2.03 ssummary: 10302 requests ok, 0 failed, median 2.03 s, p95 2.05 s, peak 6 replicas at 56 s
Scale to zero and the cold start:
126 s load stopped456 s replicas 2471 s replicas 1713 s replicas 0request 1 : HTTP 200 in 29.640707 srequest 2 : HTTP 200 in 0.137173 srequest 3 : HTTP 200 in 0.133291 s
With a minimum of one, after six idle minutes:
request 1 : HTTP 200 in 0.162562 srequest 2 : HTTP 200 in 0.107679 srequest 3 : HTTP 200 in 0.101485 s
Two details: az containerapp update printed "concurrentRequests": "" in its response, while revision show afterwards showed the stored value "10", so check the revision, not the update's output. And the Azure Monitor metric "Replicas" (per minute) showed the same story: 0 → 1 → 6 → 1 → 10.
Idle vs active billing
A replica is billed at the lower idle rate only when its revision runs at its minimum (≥ 1) and the replica processes no requests, uses less than 0.01 vCPU and receives less than 1,000 bytes per second. In West Europe, the idle vCPU rate is about an eighth of the active rate (USD 0.000004 vs 0.000034 per vCPU-second, Azure Retail Prices API, October 2026); memory costs the same in both states. Each subscription gets 180,000 vCPU-seconds, 360,000 GiB-seconds and 2 million requests free per month.
Left out of the video
Event-driven scaling with KEDA (queues, Event Hubs, cron, managed identity for scale rules), TCP scale rules, CPU and memory sizing (memory must be at least twice the CPU in GiB; memory over the limit restarts a replica, CPU over the limit throttles it), workload profiles, revision modes and traffic splitting. They are in the Microsoft Learn module below; queue scaling and traffic splitting get their own videos in this series.





