agentsclimarketplace

Deploying azure container apps

Skill alexpizarro/azure-lean-stack-skills/skills/deploying-azure-container-apps

Azure apps that cost nothing when nobody's using them. A Claude Code skill pack — 14 composable skills, 37+ documented gotchas, branch-per-env CI/CD.

Install
npx -y skills add alexpizarro/azure-lean-stack-skills --skill deploying-azure-container-apps

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Deploys Docker containers to Azure Container Apps with scale-to-zero, multi-container sidecars, shared managed environments, and Container Apps Jobs for batch workloads. Use when SWA + FC1 don't fit — long-running servers, WebSocket/SSE streaming, custom Docker runtimes, scheduled or queue-driven background jobs, or multi-process workloads with sidecars.

SKILL.md

7.4 KB, as published. Nobody here has run it

Deploying Azure Container Apps

Scale-to-zero Docker containers for workloads SWA + FC1 can't handle: long-running servers, WebSocket/SSE, streaming, custom runtimes, multi-process sidecars, and run-to-completion background jobs.

When to use Container Apps

NeedUse
CRUD API + React frontendSWA — not this
Timer/queue trigger, AI workload, < 30 minFC1 — not this
Long-running HTTP server, > 30s requestsContainer App (this skill)
WebSocket / SSE / streamingContainer App with --request-timeout 1800
Sidecar (e.g. headless browser, embedded DB)Container App multi-container
Batch / cron / one-shot processorContainer Apps Job (this skill, jobs section)
Multiple related apps sharing logs/networkingShared managed environment (this skill)

Cost defaults

StateCost
Idle (minReplicas: 0)$0
Active (0.25 vCPU, 0.5 GiB)pennies/month at low traffic
Always warm (minReplicas: 1)~$5/month (no cold start)
Log Analytics workspacebound by dailyQuotaGb on the workspace (see instrumenting-azure-app-insights)

Critical pattern: share the managed environment

A Microsoft.App/managedEnvironments resource is the unit that owns networking + Log Analytics. One environment can host many Container Apps and Jobs. Sharing saves Log Analytics workspace cost and simplifies VNet wiring.

// One env created once
module env 'modules/managedEnv.bicep' = { ... }

// Multiple apps reuse it
module appA 'modules/containerApp.bicep' = {
  params: { managedEnvironmentId: env.outputs.id, ... }
}
module appB 'modules/containerApp.bicep' = {
  params: { managedEnvironmentId: env.outputs.id, ... }
}
module syncJob 'modules/containerAppJob.bicep' = {
  params: { environmentId: env.outputs.id, ... }
}

See references/multi-app-shared-env.md.

Container Apps Jobs — scale-to-zero by nature

For run-to-completion workloads (batch processing, sync, scheduled crawls), use Microsoft.App/jobs instead of a Container App. A Job only runs when triggered; there are no idle replicas.

Triggers:

  • Manual — started via az containerapp job start (with optional --env-vars for per-run inputs)
  • Schedule — cron expression
  • Event — Azure queue/event source
resource job 'Microsoft.App/jobs@2024-03-01' = {
  properties: {
    environmentId: env.id
    workloadProfileName: 'Consumption'   // no idle billing
    configuration: {
      triggerType: 'Manual'
      replicaTimeout: 600                // seconds — max single-run duration
      replicaRetryLimit: 0
      manualTriggerConfig: {
        parallelism: 1
        replicaCompletionCount: 1
      }
    }
    template: { ... }
  }
}

Trigger with per-run inputs:

az containerapp job start \
  --name "$JOB_NAME" \
  --resource-group "$RG" \
  --env-vars "INPUT_ID=$ID" "BLOB_PATH=$PATH"

See references/aca-jobs.md.

Multi-container sidecar pattern

A Container App can run multiple containers in a single replica, sharing localhost networking. Useful for embedded dependencies like a headless browser:

template:
  containers:
    - name: app
      image: $ACR/app:$TAG
      env:
        - name: BROWSER_URL
          value: http://localhost:11235   # talks to sidecar
    - name: browser
      image: docker.io/unclecode/crawl4ai:0.8.6   # pin — avoid :latest drift

Apply via YAML to keep both containers atomic:

az containerapp update --name "$APP_NAME" --resource-group "$RG" --yaml app.yaml

See references/sidecar-pattern.md.

Secrets via secretref:

Container App secrets are write-only — values can't be read back. Link to env vars:

az containerapp secret set --name "$APP_NAME" --resource-group "$RG" \
  --secrets "openai-key=$OPENAI_API_KEY"

az containerapp update --name "$APP_NAME" --resource-group "$RG" \
  --set-env-vars "OPENAI_API_KEY=secretref:openai-key"

Gotcha: updating a secret value doesn't restart replicas. Force a revision restart:

REVISION=$(az containerapp show --name "$APP_NAME" --resource-group "$RG" \
  --query "properties.latestRevisionName" -o tsv)
az containerapp revision restart --name "$APP_NAME" --resource-group "$RG" --revision "$REVISION"

Request timeout for SSE / WebSocket

Default is 240 seconds. Streaming connections drop at 4 minutes without this:

az containerapp ingress update --name "$APP_NAME" --resource-group "$RG" --request-timeout 1800

Maximum: 3600 (1 hour).

Probes (health checks)

Liveness + readiness probes prevent traffic to a starting container and recycle stuck ones:

probes:
  - type: liveness
    httpGet: { path: /health, port: 8000 }
    initialDelaySeconds: 10
    periodSeconds: 30
  - type: readiness
    httpGet: { path: /health, port: 8000 }
    initialDelaySeconds: 5
    periodSeconds: 10

See references/probes.md.

Image registry — GHCR vs ACR

RegistryWhen
GHCR (ghcr.io)Public images, GitHub-hosted projects. Free for public repos.
Docker HubAvoid anonymously (100 pulls/6h rate limit). Pin tags if used.
ACR (Azure Container Registry)Private images, fine-grained RBAC. ~$5/month Basic tier.

For non-secret images, GHCR is the cheapest option. See references/ghcr-vs-acr.md.

Dockerfile template

FROM node:22-alpine

WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build && npm prune --production

EXPOSE 8000
ENV NODE_ENV=production

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:8000/health || exit 1

CMD ["node", "dist/server.js"]

Key points:

  • Alpine for small images (faster cold start)
  • Build with devDeps, then npm prune --production
  • Use wget not curl (Alpine doesn't bundle curl)
  • Always expose /health

Composes with

Templates

FilePurpose
templates/containerApp.bicepHTTP Container App with scale-to-zero
templates/containerAppJob.bicepManual-trigger Container Apps Job
templates/managedEnv.bicepShared managed environment (Consumption profile)
templates/multi-container.yamlYAML for sidecar pattern

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.