Revisions, Traffic Splitting & Dapr
Why this matters
Revisions and traffic splitting are how you ship safely: canary a new version to 10% of users, roll back instantly by shifting weights, or run blue/green — all without the Deployment/Service surgery you'd do in raw Kubernetes yourself (as in track 03). Dapr then lets two apps call each other by name with retries and mTLS, without you wiring service discovery yourself. Together they turn a Container Apps Environment into a small service mesh you didn't have to build.
Concepts
Single-revision vs. multiple-revision mode
Every app runs in one of two revision modes. In single mode (the
default) only the latest revision is active and gets 100% of traffic; each
update supersedes the last — a rolling update, like a plain Deployment. In
multiple mode, several revisions can be active simultaneously and you
control how traffic is divided among them. You switch modes with
az containerapp revision set-mode. Multiple mode is the prerequisite for
canary/blue-green; single mode is simpler when you don't need it. Switching to
multiple mode doesn't retroactively activate old revisions — you manage which
are active going forward.
Revisions are immutable snapshots
A revision is an immutable version of the app's template (image + config).
You've already seen that any template change mints one (module 02). In multiple
mode each active revision gets its own stable revision FQDN
(<app>--<revision-suffix>.<env>...) in addition to the app's main FQDN — so
you can hit a specific revision directly for testing, while the main FQDN
follows your traffic weights. Revisions can be activated and
deactivated; a deactivated revision keeps its config but serves no traffic
and consumes no replicas.
Traffic splitting: canary, blue/green, rollback
In multiple mode you assign traffic weights across revisions with
az containerapp ingress traffic set — e.g. 90% to the current stable, 10% to
a new canary. Blue/green is just 100/0 then 0/100. Instant rollback is shifting
weights back to the known-good revision — no redeploy, no image pull, because
the old revision is still there. This is the same idea as a Kubernetes
canary with two Deployments behind one Service and weighted routing, except
Envoy and the platform do the weighting for you and it's one command.
Dapr in Container Apps
Dapr (Distributed Application Runtime) is built into ACA and enabled
per-app (--enable-dapr, plus a stable --dapr-app-id). When enabled, each
app gets a Dapr sidecar; apps call each other through it using the app-id,
getting service discovery, retries, and mTLS for free. This is the same Dapr
you could add to any Kubernetes cluster yourself, but you don't deploy the Dapr
control plane — it's part
of the Environment. Dapr components (state stores, pub/sub brokers, secret
stores) are defined at the Environment level and scoped to specific app-ids.
Dapr service invocation between two apps
The headline pattern: app A calls app B without knowing B's address. A issues a
request to its local Dapr sidecar at
http://localhost:3500/v1.0/invoke/<B-app-id>/method/<path>, and Dapr resolves
<B-app-id> to app B within the Environment, forwards the call over mTLS, and
retries on transient failure. B just needs Dapr enabled with that app-id and to
be listening on its app port. Because discovery is by app-id, not FQDN or
IP, this only works within one Environment (recall module 01: Dapr and
internal discovery are Environment-scoped).
Command reference
| Command | What it does | Example |
|---|---|---|
az containerapp revision set-mode |
Set single/multiple revision mode | az containerapp revision set-mode --name web --resource-group rg-aca-m05 --mode multiple |
az containerapp revision list |
List revisions and their state | az containerapp revision list --name web --resource-group rg-aca-m05 -o table |
az containerapp revision copy |
Create a new revision from an existing one | az containerapp revision copy --name web --resource-group rg-aca-m05 --image <img> --revision-suffix v2 |
az containerapp revision activate / deactivate |
Turn a revision on/off | az containerapp revision deactivate --name web --resource-group rg-aca-m05 --revision web--v1 |
az containerapp ingress traffic set |
Split traffic across revisions | az containerapp ingress traffic set --name web --resource-group rg-aca-m05 --revision-weight web--v1=90 web--v2=10 |
az containerapp ingress traffic show |
Show current traffic weights | az containerapp ingress traffic show --name web --resource-group rg-aca-m05 -o table |
az containerapp create (Dapr) |
Create an app with Dapr enabled | az containerapp create ... --enable-dapr --dapr-app-id orders --dapr-app-port 80 |
Flag-by-flag breakdowns:
az containerapp revision copy --name web --resource-group rg-aca-m05 --image <img> --revision-suffix v2
--image— the image for the new revision (change it to ship a new version).--revision-suffix v2— a human-readable suffix so the revision is namedweb--v2instead of a random hash; makes traffic commands readable.
az containerapp ingress traffic set --name web --resource-group rg-aca-m05 --revision-weight web--v1=90 web--v2=10
--revision-weight web--v1=90 web--v2=10— assign percentages by revision name; must sum to 100. This is the canary/blue-green/rollback control. You can also uselatest=NNto target the latest revision.
az containerapp create --name orders --resource-group rg-aca-m05 --environment env-m05 --image ... --target-port 80 --ingress internal --enable-dapr --dapr-app-id orders --dapr-app-port 80
--enable-dapr— inject the Dapr sidecar.--dapr-app-id orders— the stable identity other apps use to invoke this one; changing it breaks callers.--dapr-app-port 80— the port Dapr forwards invoked requests to inside the container (usually your app's listening port).
Hands-on exercises
Set up group, Environment, and an app.
az group create --name rg-aca-m05 --location eastus az containerapp env create --name env-m05 --resource-group rg-aca-m05 --location eastus az containerapp create --name web --resource-group rg-aca-m05 --environment env-m05 ` --image mcr.microsoft.com/k8se/quickstart:latest --target-port 80 --ingress external ` --revision-suffix v1Switch to multiple-revision mode.
az containerapp revision set-mode --name web --resource-group rg-aca-m05 --mode multiple az containerapp show --name web --resource-group rg-aca-m05 --query properties.configuration.activeRevisionsMode -o tsvVerify it prints
Multiple.Create a second revision.
az containerapp revision copy --name web --resource-group rg-aca-m05 ` --image mcr.microsoft.com/k8se/quickstart:latest --revision-suffix v2 --set-env-vars VERSION=2 az containerapp revision list --name web --resource-group rg-aca-m05 -o tableVerify both
web--v1andweb--v2appear and are active.Canary split 90/10.
az containerapp ingress traffic set --name web --resource-group rg-aca-m05 ` --revision-weight web--v1=90 web--v2=10 az containerapp ingress traffic show --name web --resource-group rg-aca-m05 -o tableVerify the weights. Hit the FQDN repeatedly and observe ~1 in 10 requests hitting v2 (if the image surfaced the difference; otherwise trust the weights table).
Test a specific revision directly.
az containerapp revision show --name web --resource-group rg-aca-m05 --revision web--v2 --query properties.fqdn -o tsvcurlthat revision FQDN to hit v2 regardless of traffic weights — useful for smoke-testing a canary before widening it.Promote (blue/green) then instant rollback.
az containerapp ingress traffic set --name web --resource-group rg-aca-m05 --revision-weight web--v2=100 # ...decide v2 is bad... az containerapp ingress traffic set --name web --resource-group rg-aca-m05 --revision-weight web--v1=100Verify each switch in the traffic table. Note the rollback is instant — v1 was never removed.
Deactivate the old revision to stop it consuming replicas.
az containerapp revision deactivate --name web --resource-group rg-aca-m05 --revision web--v2 az containerapp revision list --name web --resource-group rg-aca-m05 -o tableVerify
web--v2shows inactive. (Reactivate withrevision activateif needed.)Deploy two Dapr-enabled apps. A backend and a caller.
az containerapp create --name backend --resource-group rg-aca-m05 --environment env-m05 ` --image mcr.microsoft.com/k8se/quickstart:latest --target-port 80 --ingress internal ` --enable-dapr --dapr-app-id backend --dapr-app-port 80 az containerapp create --name frontend --resource-group rg-aca-m05 --environment env-m05 ` --image mcr.microsoft.com/k8se/quickstart:latest --target-port 80 --ingress external ` --enable-dapr --dapr-app-id frontend --dapr-app-port 80Verify both show Dapr enabled:
az containerapp show --name frontend --resource-group rg-aca-m05 --query properties.configuration.dapr -o jsonc.Exercise Dapr service invocation. From the frontend's console (or a Dapr app that makes the call), the invocation URL is
http://localhost:3500/v1.0/invoke/backend/method/<path>. Usingaz containerapp execinto the frontend replica:az containerapp exec --name frontend --resource-group rg-aca-m05 --command "/bin/sh" # inside the container: # wget -qO- http://localhost:3500/v1.0/invoke/backend/method/Verify the call reaches
backendvia its app-id (not its FQDN/IP). If your image lacks a shell/wget, note the URL pattern is the deliverable.Diagnose and fix: revision stuck provisioning. Ship a revision whose container can't start (bad image tag):
az containerapp revision copy --name web --resource-group rg-aca-m05 ` --image mcr.microsoft.com/k8se/quickstart:doesnotexist --revision-suffix bad az containerapp revision list --name web --resource-group rg-aca-m05 -o tableObserve
web--badstuck in Provisioning/Failed and (if you routed traffic to it) errors. Diagnose withaz containerapp revision show --name web --resource-group rg-aca-m05 --revision web--bad -o jsonc(look at provisioning/health state) and the logs. Fix by shifting all traffic back to a healthy revision and deactivating the bad one:az containerapp ingress traffic set --name web --resource-group rg-aca-m05 --revision-weight web--v1=100 az containerapp revision deactivate --name web --resource-group rg-aca-m05 --revision web--badDiagnose and fix: Dapr call fails with wrong app-id. In the invocation URL, use a non-existent app-id (
.../invoke/backendd/method/). The call fails to resolve. Fix by using the correct--dapr-app-id(backend). Lesson: Dapr discovery is by app-id, and it's Environment-scoped.Cleanup.
az group delete --name rg-aca-m05 --yes --no-wait
Independent challenge
Deploy an app in multiple-revision mode, ship a second revision, and run a disciplined canary: 100/0 → 90/10 → 50/50 → 0/100, verifying weights at each step and testing the canary via its dedicated revision FQDN before widening. Then deploy a second Dapr-enabled app and demonstrate that your first app can invoke it by app-id. Combine this module with module 03: give the canary revision an HTTP scale rule and confirm it scales independently of the stable revision under load. Clean up the resource group when done.
Stuck? One hint
Each active revision scales on its own scale rules, so to see the canary scale
independently, drive load specifically at its revision FQDN (from
revision show --query properties.fqdn) rather than the app's main FQDN, and
watch replica list — replicas are reported per revision.
Common mistakes & troubleshooting
- Forgetting to switch to multiple mode.
ingress traffic setacross revisions only makes sense in multiple mode; in single mode the latest revision always gets 100%. - Traffic weights that don't sum to 100. The command expects percentages summing to 100; a leftover revision at some weight throws off the split.
- Leaving dead revisions active. Active-but-unused revisions can still hold min replicas and cost money. Deactivate old revisions you've rolled away from.
- Changing a Dapr app-id. Callers invoke by app-id; renaming it silently breaks them. Treat app-id as a stable contract.
- Expecting cross-Environment Dapr. Dapr invocation and internal discovery work only within one Environment — the same boundary rule from module 01.
- Cost pitfall: canaries with warm min-replicas. A canary revision with
--min-replicas 1bills continuously alongside stable. Keep canaries at min 0 (or deactivate them) when not actively testing.
Checkpoint quiz
Write down your answer to each question before expanding it — checking without attempting first is the single easiest way to fool yourself into thinking you've learned this.
- What must you do before you can split traffic across two revisions?
- How do you roll back a bad release instantly, and why is it instant?
- What's the difference between the app's main FQDN and a revision FQDN?
- In Dapr service invocation, what does app A use to address app B, and what does A actually connect to locally?
- Why does Dapr invocation only work within a single Environment?
- A deactivated revision — does it serve traffic? Does it consume replicas?
- You set
--revision-weight web--v2=100and the app starts erroring. Two likely causes and the fastest mitigation?
Show answers
- Switch the app to multiple-revision mode
(
revision set-mode --mode multiple); single mode always routes 100% to the latest revision. - Shift traffic weights back to the known-good revision with
ingress traffic set. Instant because the old revision is still present (immutable snapshot) — no redeploy or image pull needed. - The main FQDN follows your traffic weights across active revisions; a
revision FQDN (
<app>--<suffix>...) always hits that one specific revision, for direct testing. - A uses B's Dapr app-id; A connects to its own local Dapr sidecar
(
http://localhost:3500/v1.0/invoke/<app-id>/method/...), which resolves and forwards to B over mTLS. - Dapr discovery/invocation and internal service discovery are scoped to one Environment (the shared network/mesh boundary from module 01).
- No traffic, and no replicas — it retains config but is dormant until reactivated.
- Either v2's revision is unhealthy/stuck provisioning (bad image/config) or v2 has a bug. Fastest mitigation: shift 100% back to the healthy revision, then deactivate/investigate v2.
Next
06-secrets-managed-identity-and-config — stop putting connection strings in plaintext: secrets, Key Vault references, managed identity, and the role assignments that make them work.