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 named web--v2 instead 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 use latest=NN to 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

  1. 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 v1
    
  2. Switch 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 tsv
    

    Verify it prints Multiple.

  3. 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 table
    

    Verify both web--v1 and web--v2 appear and are active.

  4. 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 table
    

    Verify 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).

  5. Test a specific revision directly.

    az containerapp revision show --name web --resource-group rg-aca-m05 --revision web--v2 --query properties.fqdn -o tsv
    

    curl that revision FQDN to hit v2 regardless of traffic weights — useful for smoke-testing a canary before widening it.

  6. 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=100
    

    Verify each switch in the traffic table. Note the rollback is instant — v1 was never removed.

  7. 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 table
    

    Verify web--v2 shows inactive. (Reactivate with revision activate if needed.)

  8. 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 80
    

    Verify both show Dapr enabled: az containerapp show --name frontend --resource-group rg-aca-m05 --query properties.configuration.dapr -o jsonc.

  9. 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>. Using az containerapp exec into 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 backend via its app-id (not its FQDN/IP). If your image lacks a shell/wget, note the URL pattern is the deliverable.

  10. 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 table
    

    Observe web--bad stuck in Provisioning/Failed and (if you routed traffic to it) errors. Diagnose with az 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--bad
    
  11. Diagnose 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.

  12. 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 set across 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 1 bills 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.

  1. What must you do before you can split traffic across two revisions?
  2. How do you roll back a bad release instantly, and why is it instant?
  3. What's the difference between the app's main FQDN and a revision FQDN?
  4. In Dapr service invocation, what does app A use to address app B, and what does A actually connect to locally?
  5. Why does Dapr invocation only work within a single Environment?
  6. A deactivated revision — does it serve traffic? Does it consume replicas?
  7. You set --revision-weight web--v2=100 and the app starts erroring. Two likely causes and the fastest mitigation?
Show answers
  1. Switch the app to multiple-revision mode (revision set-mode --mode multiple); single mode always routes 100% to the latest revision.
  2. 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.
  3. The main FQDN follows your traffic weights across active revisions; a revision FQDN (<app>--<suffix>...) always hits that one specific revision, for direct testing.
  4. 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.
  5. Dapr discovery/invocation and internal service discovery are scoped to one Environment (the shared network/mesh boundary from module 01).
  6. No traffic, and no replicas — it retains config but is dormant until reactivated.
  7. 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.