container-apps-deployment
Skill5 starsChanged 46 days agoMITView on GitHub
Container Apps deployment gotchas and SPA frontend patterns for Azure. Supplements the official azure-prepare plugin skill with additional patterns for zone redundancy, azure.yaml configuration, and SPA frontend deployment (VITE_API_URL). USE FOR: Container Apps zone redundancy errors, managed-identity ACR pulls, azure.yaml language fields, cross-platform hooks, SPA frontend VITE_API_URL, React deployment to Azure, and ARM64-to-AMD64 builds. DO NOT USE FOR: generating complete Bicep infrastructure from scratch (use azure-prepare plugin) or AKS deployments (use superset-azure).
- Reads credentials
1 other project uses a version of this, 1 of them edited.
Adopted by
Most recently taken up by
- DanWahlin/github-azure-agentic-journeysedited, first seen Jul 21, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 1
- DanWahlin/github-azure-agentic-journeys★ 3799% same
Wrote their own version · 0
None found.
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository11 other files it gives its agents.
- AGENTS.mdAGENTS.md
- journeys/smart-todo/setup/copilot-instructions.mdCopilot instructions
- data-access-abstractionSkill
- grafana-azureSkill
- grill-planSkill
- journey-runnerSkill
- journey-templateSkill
- journey-test-harnessSkill
- n8n-azureSkill
- superset-azureSkill
- flex-functions-sql-foundrySkill
The file
We split this skill into the rules it gives an agent
5 rules. About 0 are common, 2 are distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 75
When deploying a React/Vite frontend and API as separate Container Apps, the frontend needs the API's URL baked in at build time.
- Very specific: line 96
Read API_URL, AZURE_CONTAINER_REGISTRY_ENDPOINT, and RESOURCE_GROUP_NAME with azd env get-value.
- Very specific: line 98
Run az acr build with --platform linux/amd64, a unique image tag, and --build-arg VITE_API_URL=<API_URL>/api.
- Distinctive: line 99
Update the web Container App to use the cloud-built image.
- Very specific: line 100
Wait until the expected revision is healthy and provisioned, then verify the storefront can load products.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Container Apps Deployment Patterns
Supplements the official azure-prepare plugin skill with additional gotchas for Container Apps deployments — zone redundancy, azure.yaml, and SPA frontend patterns.
📖 ACR authentication: Use a deterministic two-phase pattern with separate deployment modules. The bootstrap module provisions each Container App with a public placeholder image and system-assigned identity but no
configuration.registriesentry. Grant that principalAcrPull, then make a later module depend on the role assignment and update the same app with the ACR login server andidentity: 'system'before deploying private images. A single module that declares both a new system identity and registry is not two-phase and can fail with an ACR token-exchange 401. Some azd versions callaz containerapp registry setautomatically and some do not. Never assume that implicit step occurred; verify the registry configuration before the first private-image deployment.
Region-Specific Gotchas
Container Apps Environment zoneRedundant
The AVM module br/public:avm/res/app/managed-environment may default zoneRedundant to true. Several regions (including westus) don't support it.
module cae 'br/public:avm/res/app/managed-environment:0.8.1' = {
params: {
// ...
zoneRedundant: false // Required for westus and other regions without zone support
}
}Without this: azd up fails with "Zone redundancy is not currently supported in this region".
azure.yaml Configuration
Docker services require language field
azd requires either language or image on each service, even when docker.path is specified:
services:
api:
host: containerapp
language: ts # REQUIRED — azd won't infer from Dockerfile
docker:
path: api/Dockerfile
context: api
remoteBuild: truedocker.path and docker.context are relative to the service's project folder. Either omit project and use repository-relative paths, as above, or set project: api with path: Dockerfile and context: .. Mixing the two doubles the folder, and azd up fails at publish with lstat .../api/api: no such file or directory.
Declare each service whose image azd owns this way. AIMarket declares only api; Bicep creates its web Container App and the project postdeploy hook owns the storefront ACR build and update. Without language: azd up fails with "must specify language or image". Without remoteBuild: true: azd can require a local Docker daemon.
Public images: no azd service
When a Container App runs a public image that you don't build (Grafana, n8n), set the image in Bicep and leave services: out of azure.yaml, so azure.yaml has only infra and hooks. azd provision (or azd up) then creates everything. A service with image: grafana/grafana makes azd pull the image through a local Docker daemon during packaging, so the deployment requires Docker, and on Windows, where Docker often runs Windows containers, it fails with no matching manifest for windows.
Cross-platform hooks
This repository requires azd 1.28.0 or later and Node.js LTS or later. Use JavaScript or TypeScript hooks referenced directly from azure.yaml; azd detects the language from the extension. Do not generate Bash-only .sh or PowerShell-only .ps1 lifecycle hooks.
hooks:
postprovision:
run: infra/hooks/postprovision.js
postdeploy:
run: infra/hooks/postdeploy.jsUse postprovision for steps that need infrastructure outputs, such as setting WEBHOOK_URL. Use postdeploy for steps that need deployed services, such as rebuilding a frontend with its API URL. Hook code must invoke az and azd through argument arrays, never interpolated shell command strings. On Mac and Linux, call the CLI executable directly. On Windows, .cmd shims cannot be launched with execFileSync() or spawnSync() alone. Invoke a static, non-interpolated powershell.exe runner and pass the command plus arguments as a JSON environment payload, then use PowerShell's call operator with array splatting. This supports both the Azure CLI shim and the azd.exe installation without exposing arguments to shell parsing. Build deployment images in Azure Container Registry so the host does not need Docker or Buildx.
SPA Frontend Deployment (React/Vite)
The VITE_API_URL Problem
When deploying a React/Vite frontend and API as separate Container Apps, the frontend needs the API's URL baked in at build time. But the API URL isn't known until after provisioning.
Symptoms:
- Frontend shows
Unexpected token '<', "<!doctype "... is not valid JSON - The React app calls
/api/productson the web container (nginx), which returnsindex.html
Root Cause: VITE_API_URL defaults to /api (the Vite dev proxy). In production, nginx has no /api route — it serves the SPA for all paths.
Solution: Cross-platform post-deploy hook
Do not leave this as a manual learner step on first success. Generate infra/hooks/postdeploy.js and reference it directly from azure.yaml:
hooks:
postdeploy:
run: infra/hooks/postdeploy.jsThe JavaScript hook must:
- Resolve the application root from CommonJS
__dirnamerather than assuming the current working directory. - Read
API_URL,AZURE_CONTAINER_REGISTRY_ENDPOINT, andRESOURCE_GROUP_NAMEwithazd env get-value. - Find the web Container App by its
azd-service-name=webtag. - Run
az acr buildwith--platform linux/amd64, a unique image tag, and--build-arg VITE_API_URL=<API_URL>/api.--fileis resolved from the current directory, not from the build context, so pass the Dockerfile's full path (--file client/Dockerfilewith theclientcontext, or an absolute path).--file Dockerfilefails withUnable to find 'Dockerfile'. - Update the web Container App to use the cloud-built image.
- Wait until the expected revision is healthy and provisioned, then verify the storefront can load products. When
minReplicasis0, accept bothRunningandScaledToZero; requiring onlyRunningcauses a false timeout before the verification request can activate the revision.
Call external tools with execFileSync() or spawnSync() and argument arrays. Do not concatenate a shell command, use chmod, or depend on Bash, cut, grep, or date. The static Windows PowerShell launcher described above is the only platform-specific exception; all CLI arguments must travel in the JSON environment payload. Windows PowerShell cannot losslessly pass a literal double quote inside a native argument, so reject it for every Windows target. For .cmd/.bat, also reject &, |, <, >, ^, %, !, (, ), and CR/LF. Rewrite unsafe arguments or use attached files; native .exe targets preserve the remaining metacharacters. Use JavaScript for path handling, timestamps, retries, and JSON parsing.
For a storefront-only rebuild, run node infra/hooks/postdeploy.js explicitly and verify production product loading. In the AIMarket pattern the web Container App is not an azd service, so azd deploy web is not a valid command.
After the first green deploy, explain why the hook exists. Don't make the learner discover a blank product grid first.
Frontend Dockerfile Requirements
FROM node:24-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
ARG VITE_API_URL
ENV VITE_API_URL=$VITE_API_URL
RUN npm run build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80Keys: ARG + ENV must appear before RUN npm run build so Vite picks up the URL. The postdeploy hook sends this Dockerfile to an ACR linux/amd64 cloud build.
nginx.conf — SPA Only
server {
listen 80;
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}
}Do NOT add an /api/ proxy block. The frontend calls the API directly via VITE_API_URL. Adding a proxy to an internal hostname will fail because Container Apps don't resolve each other by name without VNet.
Frontend API Client Pattern
const API_BASE = import.meta.env.VITE_API_URL || '/api';
export async function getProducts() {
const res = await fetch(`${API_BASE}/products`);
return res.json();
}In dev: VITE_API_URL is unset → falls back to /api → Vite proxy handles it.
In prod: VITE_API_URL is https://ca-api-xxx.azurecontainerapps.io/api → calls API directly.
.dockerignore Requirements
Every build context sent to ACR needs a .dockerignore that excludes dependency directories (node_modules/), build output, local database files, .env files, and Git metadata (.git/). Keep the files the in-container build itself needs — for the default Node.js stack, do not exclude tsconfig.json or package*.json, or the container's TypeScript build fails. An unfiltered context can also overwhelm the ACR build upload.
ARM64 Host Cross-Compilation
Azure Container Apps runs Linux AMD64. This applies to Apple Silicon, Windows ARM64, and Linux ARM64 hosts.
Require ACR cloud builds targeting linux/amd64. Do not require Docker, Buildx, AMD64 emulation, or privileged QEMU/binfmt handlers on the host.
Without an AMD64 target, the container can crash with exec format error. ACR builds remove that host-architecture dependency.
Bicep uniqueString() Length Contracts
Azure's uniqueString() always returns 13 characters. When passing that value into a module, declare the module parameter with both @minLength(13) and @maxLength(13). Otherwise Bicep can emit false BCP334 warnings when the parameter is used in constrained resource names.
Bicep Output Naming Convention
Outputs must use SCREAMING_SNAKE_CASE for azd to pick them up:
output API_URL string = 'https://${apiApp.outputs.fqdn}'
output WEB_URL string = 'https://${webApp.outputs.fqdn}'
output AZURE_CONTAINER_REGISTRY_ENDPOINT string = acr.outputs.loginServer
output RESOURCE_GROUP_NAME string = rg.nameWrong naming → azd env get-value returns "key not found".
Transient az containerapp registry set errors
Right after a Container App's system identity is created, az containerapp registry set --identity system can return InternalServerError even though the registry entry was saved. In post-provision hooks, retry once, then confirm with az containerapp registry list before failing, so the hook doesn't stop before configuring the next app.
az acr build on Windows: use --no-logs
az acr build streams the remote build log through the local console. On Windows, where the console often uses a legacy code page, a character such as ✓ in the log makes az crash with UnicodeEncodeError: 'charmap' codec can't encode character, even though the build itself succeeds in Azure. Setting PYTHONIOENCODING doesn't help, because the Azure CLI bundles its own Python. In hooks, pass --no-logs: the command still waits for the build and returns its exit code, and it prints nothing that can crash. The same crash can hit az aks command invoke when the remote command prints non-ASCII text, such as Helm's banner.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

