Azure App Service skill for Codex

Ship through the staging slot, never around it. Codex deploys App Service web apps to a staging slot, checks the health route, and swaps with preview so production settings are applied before traffic moves. Secrets become Key Vault references read by a managed identity, environment-specific values are sticky, and startup command, port and runtime are set explicitly so boot failures are diagnosable.

  • Cloud Platforms
  • $azure-app-service
  • 178 lines
  • 2 files
  • ~2,000 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- azure-app-service

Installs to ~/.agents/skills/azure-app-service/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $azure-app-service, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Deploys and operates web apps on Azure App Service with az CLI and GitHub Actions - Linux plans and runtimes, zip and container deploys, staging deployment slots with sticky settings and swap warm-up, app settings and Key Vault references, health check path, managed identity, Always On, scaling and log streaming. Use when deploying a Node, Python, .NET or Java app or container to App Service, setting up zero-downtime slot swaps, fixing startup failures or 503s, or moving secrets out of app settings.

Use it when

  • Deploying code or a container to Azure App Service
  • Setting up zero-downtime releases with deployment slots
  • An App Service app fails to start or returns 503
  • Secrets sit in plain app settings

Use something else when

  • The workload is event-driven functions: use azure-functions
  • You need to define the plan and app in infrastructure as code: use azure-bicep

How to check the result

The staging slot answers its health route before the swap, production answers after it, and every Key Vault reference shows a resolved status.

The mistake it prevents

Forgetting to mark environment-specific settings as slot settings, so a swap carries staging's database connection into production.

~/.agents/skills/azure-app-service/SKILL.md

--- name: azure-app-service description: Deploys and operates web apps on Azure App Service with az CLI and GitHub Actions - Linux plans and runtimes, zip and container deploys, staging… license: MIT ---

Done well means: code reaches production only through a slot swap after the staging slot answered its health check, secrets are Key Vault references resolved by a managed identity, environment-specific settings are marked sticky, and the app’s startup command, port and runtime are explicit rather than guessed by the platform.

Inspect first

  1. Read AGENTS.md, the build scripts, any .github/workflows/* deploy job and any Bicep or Terraform that owns the app. Infrastructure changes go there.
  2. Read the live configuration (needs network and az login):
az webapp list -g "$RG" -o table
az webapp show -g "$RG" -n "$APP" --query "{plan:appServicePlanId, state:state, host:defaultHostName, httpsOnly:httpsOnly}"
az webapp config show -g "$RG" -n "$APP" \
  --query "{stack:linuxFxVersion, alwaysOn:alwaysOn, health:healthCheckPath, startup:appCommandLine, ftps:ftpsState}"
az webapp deployment slot list -g "$RG" -n "$APP" -o table
az webapp list-runtimes --os linux -o table

Treat app setting values as secrets: list names, not values, in chat.

Plan and tier

NeedMinimum
Deployment slots, autoscaleStandard (S1) or Premium v3 (P0v3+)
VNet integration, private endpointsBasic for VNet integration; Premium v3 recommended
Production with predictable latencyPremium v3; Always On enabled
Dev/test onlyBasic or Free (no slots, no Always On on Free)

Several apps on one plan share its CPU and memory. Put noisy or critical apps on their own plan.

Create

az appservice plan create -g "$RG" -n "$PLAN" --is-linux --sku P0v3
az webapp create -g "$RG" -p "$PLAN" -n "$APP" --runtime "NODE:22-lts" --assign-identity "[system]"
az webapp update -g "$RG" -n "$APP" --https-only true
az webapp config set -g "$RG" -n "$APP" --always-on true --ftps-state Disabled \
  --min-tls-version 1.2 --startup-file "node dist/server.js"
az webapp config set -g "$RG" -n "$APP" --generic-configurations '{"healthCheckPath": "/healthz"}'
az webapp deployment slot create -g "$RG" -n "$APP" --slot staging --configuration-source "$APP"

Use a runtime string exactly as printed by az webapp list-runtimes; the format differs between versions of the CLI and stacks.

Container contract

  • The app listens on the port in PORT (code-based) or the port in WEBSITES_PORT (custom containers, when it is not 80 or 8080).
  • Bind to 0.0.0.0, not localhost.
  • Startup must finish within the container start time limit (default 230 seconds; raise with WEBSITES_CONTAINER_START_TIME_LIMIT only after fixing slow boot).
  • Pull images with the app’s managed identity: grant it AcrPull on the registry and set acrUseManagedIdentityCreds to true in the site config.

App settings and secrets

az webapp config appsettings set -g "$RG" -n "$APP" --settings \
  NODE_ENV=production \
  DATABASE_URL="@Microsoft.KeyVault(VaultName=shop-kv;SecretName=database-url)"

# Settings that must stay with the slot (not travel with the code on swap)
az webapp config appsettings set -g "$RG" -n "$APP" --slot staging \
  --slot-settings FEATURE_FLAGS_ENV=staging DATABASE_URL="@Microsoft.KeyVault(VaultName=shop-kv-stg;SecretName=database-url)"
  • The app identity needs the Key Vault Secrets User role (RBAC vaults) on the vault.
  • Check resolution: the portal shows a status per reference; an unresolved reference arrives in the app as the literal @Microsoft.KeyVault(...) string.
  • Omit the secret version so rotation is picked up (App Service re-fetches periodically, and on restart).
  • Every settings change restarts the app. Batch changes and do them in staging first.
  • SCM_DO_BUILD_DURING_DEPLOYMENT=true asks the platform (Oryx) to run the install and build on deploy. Prefer building in CI and deploying the built artifact.

Deploy to staging, then swap

npm ci && npm run build && zip -r app.zip dist package.json package-lock.json node_modules
az webapp deploy -g "$RG" -n "$APP" --slot staging --src-path app.zip --type zip
curl -fsS "https://$APP-staging.azurewebsites.net/healthz"

# Swap with preview: applies production settings to staging and warms it up first
az webapp deployment slot swap -g "$RG" -n "$APP" --slot staging --target-slot production --action preview
# verify staging again under production settings, then:
az webapp deployment slot swap -g "$RG" -n "$APP" --slot staging --target-slot production --action swap
# abandon instead: --action reset

A swap to production is a production deploy: show the command and wait for user confirmation. Rollback is the same swap run again, since the previous version now sits in staging.

Swap warm-up: App Service pings / on each instance before switching. Point it at a real readiness route with WEBSITE_SWAP_WARMUP_PING_PATH=/healthz and WEBSITE_SWAP_WARMUP_PING_STATUSES=200.

GitHub Actions with OIDC (no publish profiles):

permissions: { id-token: write, contents: read }
steps:
  - uses: azure/login@v2
    with:
      client-id: ${{ secrets.AZURE_CLIENT_ID }}
      tenant-id: ${{ secrets.AZURE_TENANT_ID }}
      subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
  - uses: azure/webapps-deploy@v3
    with: { app-name: shop-web, slot-name: staging, package: app.zip }

Federated credential setup for the workflow identity lives in azure-entra-id.

Health check and scaling

  • With healthCheckPath set, instances that fail it repeatedly are removed from the load balancer and eventually replaced. The route must be cheap and not depend on every downstream; a DB outage should not mark all instances dead at once.
  • Health check acts only when there are 2+ instances. Run production with at least 2.
  • Scale out with autoscale rules on CPU or HTTP queue length, or automatic scaling on Premium v3; scale up (bigger SKU) when memory per instance is the limit.

Troubleshooting

az webapp log config -g "$RG" -n "$APP" --docker-container-logging filesystem
az webapp log tail -g "$RG" -n "$APP"
az webapp log download -g "$RG" -n "$APP" --log-file logs.zip
SymptomLikely cause
“Application Error” / 503 right after deployWrong startup command or port; app listening on localhost
Container did not respond to HTTP pingsWEBSITES_PORT mismatch or boot slower than start limit
Works in staging, fails after swapSetting that should be sticky was not, or vice versa
Secret value looks like @Microsoft.KeyVault(...)Identity lacks vault role, or vault firewall blocks App Service
First request after idle is slowAlways On off, or no warm-up path

Gotchas

  • ARR affinity cookies pin users to an instance; turn off with --client-affinity-enabled false for stateless apps so scale-out actually spreads load.
  • Local disk under /home is shared, slow network storage on Linux; do not use it as a cache or database.
  • Slot-specific settings changed during swap trigger restarts on the source slot; that is why preview mode exists.
  • Deleting a slot or app deletes its content and settings; confirm with the user.
  • Basic auth for SCM/FTP should be disabled once CI uses OIDC.

Verification

  • curl against the staging slot returns 200 from /healthz before the swap.
  • After swap, production /healthz and a key user route return 200; the previous build is in staging for rollback.
  • Key Vault references show resolved status; no secret values are stored in plain settings.

Report template

**App:** shop-web (Linux, P0v3 x2, NODE:22-lts), httpsOnly, Always On, health /healthz
**Deploy:** zip to staging -> swap with preview -> production (approved by user)
**Settings:** 6 app settings, 2 sticky (FEATURE_FLAGS_ENV, DATABASE_URL), 2 Key Vault references resolved
**Identity:** system-assigned, Key Vault Secrets User on shop-kv
**Rollback:** az webapp deployment slot swap --slot staging --target-slot production

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Azure App Service"
  short_description: "Ship through the staging slot, never around it"
  default_prompt: "$azure-app-service Set up a staging slot for our Azure web app and a GitHub Actions deploy that swaps after a health check"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem