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.

  • nameazure-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

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.

~/.claude/skills/azure-container-apps/SKILL.md

--- name: azure-container-apps description: Deploys and operates Azure Container Apps - environments (consumption-only versus workload profiles, VNet, internal), container sizing,… license: MIT ---

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 update with 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

NeedEnvironment
Simple public apps, pay per useWorkload profiles environment using the Consumption profile
Steady load, bigger or GPU containers, predictable costAdd Dedicated workload profiles to the same environment
Private backends, access to VNet resourcesCustom VNet (subnet delegated to Microsoft.App/environments)
No public endpoint at allInternal 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)

WorkloadRule
HTTP APIhttp with concurrentRequests (start 30-100 per replica, tune from load tests)
Raw TCPtcp with concurrentConnections
Service Bus / Storage Queue / Event Hubs workerCustom KEDA scaler (azure-servicebus, azure-queue, azure-eventhub) with managed identity auth
Kafka, Redis, Postgres, cron windowsMatching KEDA scaler as a custom rule
CPU / memorySupported, 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 build or 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

SymptomCauseFix
Revision “Provisioning failed” / image pull errorIdentity lacks AcrPull, wrong registry config, tag missingRole assignment; az containerapp registry set; verify tag
Revision unhealthy, restartsProbe path/port wrong, app listens on 127.0.0.1, slow startListen on 0.0.0.0:targetPort; startup probe with enough failureThreshold
404 from the FQDNIngress disabled/internal, traffic weight on an inactive revisionCheck ingress and traffic
Secret value not updatedSecrets are app-scopeRestart revision
Key Vault reference failsIdentity lacks Secrets User role or vault firewall blocksAssign role; allow trusted services/VNet
Never scales outRule metadata wrong, scaler auth failing--type system logs show KEDA errors
First request slowScale to zero cold startminReplicas: 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

  • targetPort must 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: true plus 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.

esc