Azure Container Apps skill for Claude Code
Revisions, KEDA scaling and secrets without passwords. Claude defines Container Apps in YAML or Bicep with managed identity for ACR and Key Vault, real startup and readiness probes, and ingress limited to what is needed. Scaling uses HTTP or KEDA queue rules tuned per workload, releases run as revisions with explicit traffic weights and labels, and debugging starts from system logs instead of guesswork.
- name
azure-container-apps - size255 lines · ≈2,850 tokens
- files1
- licenseMIT
- updated
What Claude sees before loading it
This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.
description: Deploys and operates Azure Container Apps - environments (consumption-only versus workload profiles, VNet, internal), container sizing, ingress and custom domains, revisions in single or multiple mode with traffic splitting and labels, KEDA scale rules for HTTP, queues and custom scalers including scale to zero, secrets with Key Vault references and managed identity, ACR pulls without passwords, health probes, Dapr sidecars and components, Container Apps jobs, and log debugging with az containerapp. Use when deploying a container to Azure Container Apps, writing its YAML or Bicep, configuring scaling or revisions, or when an app will not start, scale or receive traffic. Try asking
- Deploy our API to Azure Container Apps pulling from ACR with a managed identity
- Scale this Container Apps worker on Service Bus queue length and down to zero
- Roll out a new revision to 10% of traffic and roll back if errors rise
Use it when
- Deploying a container to Azure Container Apps
- Configuring KEDA scale rules, revisions or traffic splitting
- Wiring Key Vault secrets, ACR and managed identity
- A revision fails to provision, start, scale or receive traffic
Skip it when
- The workload is function-shaped with triggers and bindings — use azure-functions
- You run your own AKS or Kubernetes cluster — use kubernetes-deploy
How to check the result
The new revision reports healthy and running, traffic weights match the plan, system logs show no pull, probe or scaler errors, and moving weight back restores the previous revision.
The common mistake it prevents
Updating a secret and expecting running replicas to see it, when secrets are app-scope and only take effect after the revision restarts.
A Container App is done when it pulls from ACR with a managed identity, reads secrets from Key Vault references, exposes only the ingress it needs, passes startup and readiness probes, scales on a rule that tracks real load, and ships new versions as revisions that can take a slice of traffic and roll back by moving weights.
Ground rules
- Use managed identity for ACR pulls, Key Vault and Azure SDK access. No registry passwords or connection strings in env vars.
- Prefer multiple-revision mode for production services that need canaries or instant rollback; single mode is fine for internal tools and workers.
- Keep the app definition in code (YAML, Bicep or Terraform).
az containerapp updatewith ad-hoc flags is for experiments. - Shifting production traffic, deactivating revisions, deleting apps or environments, and changing ingress from internal to external require explicit user confirmation.
- Scale-to-zero means cold starts; for latency-sensitive HTTP services set
minReplicas >= 1.
Workflow
- [ ] 1. Inspect environment, app, revisions, and recent logs
- [ ] 2. Choose environment type and networking
- [ ] 3. Define the app: image, resources, probes, ingress, identity, secrets
- [ ] 4. Configure scaling rules
- [ ] 5. Plan revisions and traffic for releases
- [ ] 6. Add Dapr or jobs if needed
- [ ] 7. Deploy from CI; verify revision health and traffic
1. Inspect
az containerapp show -n api -g rg-prod --query "{fqdn:properties.configuration.ingress.fqdn, mode:properties.configuration.activeRevisionsMode, latest:properties.latestRevisionName, ready:properties.latestReadyRevisionName}"
az containerapp revision list -n api -g rg-prod -o table
az containerapp ingress traffic show -n api -g rg-prod
az containerapp logs show -n api -g rg-prod --type system --tail 50 # provisioning, probe, scaling events
az containerapp logs show -n api -g rg-prod --follow # console output
2. Environment
| Need | Environment |
|---|---|
| Simple public apps, pay per use | Workload profiles environment using the Consumption profile |
| Steady load, bigger or GPU containers, predictable cost | Add Dedicated workload profiles to the same environment |
| Private backends, access to VNet resources | Custom VNet (subnet delegated to Microsoft.App/environments) |
| No public endpoint at all | Internal environment + private DNS / Front Door or App Gateway in front |
Apps in the same environment share a network and Log Analytics workspace and can call each other by app name over internal ingress.
3. App definition (YAML)
# app.yaml - deploy with: az containerapp create -n api -g rg-prod --yaml app.yaml
properties:
managedEnvironmentId: /subscriptions/<sub>/resourceGroups/rg-prod/providers/Microsoft.App/managedEnvironments/cae-prod
workloadProfileName: Consumption
configuration:
activeRevisionsMode: Multiple
ingress:
external: true
targetPort: 8080
transport: auto
allowInsecure: false
traffic:
- latestRevision: true
weight: 100
registries:
- server: acmeprod.azurecr.io
identity: system
secrets:
- name: db-url
keyVaultUrl: https://kv-acme-prod.vault.azure.net/secrets/db-url
identity: system
template:
revisionSuffix: v42
containers:
- name: api
image: acmeprod.azurecr.io/api:3f2c1ab
resources: { cpu: 0.5, memory: 1Gi }
env:
- name: DATABASE_URL
secretRef: db-url
- name: PORT
value: "8080"
probes:
- type: Startup
httpGet: { path: /healthz, port: 8080 }
periodSeconds: 5
failureThreshold: 30
- type: Readiness
httpGet: { path: /ready, port: 8080 }
periodSeconds: 10
- type: Liveness
httpGet: { path: /healthz, port: 8080 }
periodSeconds: 15
scale:
minReplicas: 1
maxReplicas: 20
rules:
- name: http
http:
metadata:
concurrentRequests: "50"
identity:
type: SystemAssigned
Identity bootstrap order matters: the identity must exist and hold AcrPull on the
registry and Key Vault Secrets User on the vault before the revision can start. Either
create with a user-assigned identity that already has the roles (simplest), or create
with a public placeholder image, assign roles, then update.
az containerapp identity assign -n api -g rg-prod --system-assigned
PRINCIPAL=$(az containerapp show -n api -g rg-prod --query identity.principalId -o tsv)
az role assignment create --assignee "$PRINCIPAL" --role AcrPull --scope "$(az acr show -n acmeprod --query id -o tsv)"
az role assignment create --assignee "$PRINCIPAL" --role "Key Vault Secrets User" --scope "$(az keyvault show -n kv-acme-prod --query id -o tsv)"
az containerapp registry set -n api -g rg-prod --server acmeprod.azurecr.io --identity system
Sizing: CPU and memory come in fixed pairs on Consumption (0.25 vCPU / 0.5 Gi up to 4 vCPU / 8 Gi, memory = 2x CPU). Set runtime heap limits below the container memory.
4. Scaling (KEDA)
| Workload | Rule |
|---|---|
| HTTP API | http with concurrentRequests (start 30-100 per replica, tune from load tests) |
| Raw TCP | tcp with concurrentConnections |
| Service Bus / Storage Queue / Event Hubs worker | Custom KEDA scaler (azure-servicebus, azure-queue, azure-eventhub) with managed identity auth |
| Kafka, Redis, Postgres, cron windows | Matching KEDA scaler as a custom rule |
| CPU / memory | Supported, but cannot scale to zero |
az containerapp update -n worker -g rg-prod \
--min-replicas 0 --max-replicas 30 \
--scale-rule-name orders-queue --scale-rule-type azure-servicebus \
--scale-rule-metadata "queueName=orders" "namespace=sb-acme-prod" "messageCount=20" \
--scale-rule-identity system
Defaults to know: custom scalers poll every 30 s; cooldown before scaling the last
replica to zero is 300 s; maxReplicas defaults to 10 if unset. Check the current
Container Apps quotas for the hard ceiling. Flag names for identity-based scale auth are
recent; confirm with az containerapp update --help on the installed CLI extension.
5. Revisions and traffic
Revision-scope changes (image, env, resources, probes, scale) create a new revision.
App-scope changes (ingress, secrets, registries, Dapr settings) do not; after changing a
secret value, restart the revision:
az containerapp revision restart -n api -g rg-prod --revision api--v42.
Canary in multiple-revision mode:
az containerapp update -n api -g rg-prod --image acmeprod.azurecr.io/api:9a8b7c6 --revision-suffix v43
az containerapp revision show -n api -g rg-prod --revision api--v43 --query "properties.{health:healthState,running:runningState,replicas:replicas}"
az containerapp ingress traffic set -n api -g rg-prod --revision-weight api--v42=90 api--v43=10 # confirm for prod
az containerapp revision label add -n api -g rg-prod --label canary --revision api--v43 # stable URL for testing
# promote
az containerapp ingress traffic set -n api -g rg-prod --revision-weight api--v43=100
# rollback = move weight back to the previous revision, then deactivate the bad one
az containerapp revision deactivate -n api -g rg-prod --revision api--v43
Pin traffic to named revisions during canaries; latestRevision: true sends 100% to
whatever deploys next. Old active revisions still consume resources if they have
minReplicas > 0; deactivate them after promotion.
6. Dapr and jobs
Dapr: enable per app (--enable-dapr --dapr-app-id api --dapr-app-port 8080) and define
components at the environment level, scoped to the apps that need them:
az containerapp env dapr-component set -n cae-prod -g rg-prod --dapr-component-name pubsub --yaml pubsub.yaml
Use Dapr when you want its building blocks (pub/sub, state, service invocation with mTLS) across several apps; skip it for a single service calling Azure SDKs directly.
Jobs for finite work:
az containerapp job create -n nightly-report -g rg-prod --environment cae-prod \
--trigger-type Schedule --cron-expression "0 3 * * *" \
--image acmeprod.azurecr.io/report:3f2c1ab --cpu 1 --memory 2Gi \
--replica-timeout 1800 --replica-retry-limit 1 --mi-system-assigned
az containerapp job start -n nightly-report -g rg-prod
az containerapp job execution list -n nightly-report -g rg-prod -o table
Event-triggered jobs (queue-driven, KEDA rules) fit long-running per-message work better than a scaled app.
7. Deploy from CI
- Build and push to ACR (
az acr buildor docker buildx), tag with the git SHA. - Authenticate with OIDC federated credentials (
azure/login), not a client secret. az containerapp update -n api -g rg-prod --image ...:${SHA} --revision-suffix ${SHA:0:7}or apply the YAML/Bicep with the new image.- Wait until the new revision reports healthy and running before shifting traffic.
Debugging
| Symptom | Cause | Fix |
|---|---|---|
| Revision “Provisioning failed” / image pull error | Identity lacks AcrPull, wrong registry config, tag missing | Role assignment; az containerapp registry set; verify tag |
| Revision unhealthy, restarts | Probe path/port wrong, app listens on 127.0.0.1, slow start | Listen on 0.0.0.0:targetPort; startup probe with enough failureThreshold |
| 404 from the FQDN | Ingress disabled/internal, traffic weight on an inactive revision | Check ingress and traffic |
| Secret value not updated | Secrets are app-scope | Restart revision |
| Key Vault reference fails | Identity lacks Secrets User role or vault firewall blocks | Assign role; allow trusted services/VNet |
| Never scales out | Rule metadata wrong, scaler auth failing | --type system logs show KEDA errors |
| First request slow | Scale to zero cold start | minReplicas: 1 |
az containerapp exec -n api -g rg-prod --command sh # shell into a running replica
Log Analytics tables: ContainerAppConsoleLogs_CL and ContainerAppSystemLogs_CL
(or the newer non-_CL tables depending on the environment’s logging setup).
Gotchas
targetPortmust match the port the app listens on; the platform does not detect it.- HTTP scale rules count concurrent requests per replica averaged over a short window; long-polling or streaming connections inflate concurrency.
- Multiple revisions with
latestRevision: trueplus weights on named revisions can produce surprising splits; set explicit weights during releases. - Environment VNet subnet size limits how many replicas can run; plan for max replicas of all apps plus surge.
- Changing a workload profile or VNet configuration usually means a new environment; plan migrations as blue/green between environments.
Output
**App:** api in cae-prod (workload profiles, Consumption), external ingress :8080, custom domain + managed cert
**Identity:** system-assigned; AcrPull on acmeprod; Key Vault Secrets User on kv-acme-prod
**Scaling:** http 50 concurrent, min 1 / max 20; worker on azure-servicebus messageCount 20, min 0
**Release:** multiple revisions; v43 canary 10% (label canary), promoted after 30 min clean
**Verified:** revision healthy/running; error rate flat during canary; rollback by weight tested About this skill
Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.