Skip to main content

azd-patterns

Tips and patterns for Azure Developer CLI (azd) workflows, ACA job deployment, and infrastructure scripting conventions. USE FOR: azd hooks, postdeploy, postprovision, ACA job deployment, container app job image update, publish_aca, deploy_job, azure.yaml hooks, cross-platform deployment scripts, uv run, azd env, azd conventions, infra scripts, MCAPS subscription, SecurityControl Ignore tag, resource group tagging, Defender for Cloud noise, Azure Policy auto-remediation, AZURE_TAGS, pilot posture, demo subscription tagging, AcrPull, ImagePullError, JSON array param, BCP186, FetchingKeyVaultSecretFailed, dependsOn rbac, image-pull credential, ACA placeholder image, azd env set triple-escape. DO NOT USE FOR: az login, tenant switching, subscription isolation (use azure-tenant-isolation), Foundry agents (use microsoft-foundry).

설치로 이동

소스 정보

저장소
aiappsgbb/awesome-gbb
최근 소스 활동
2026년 9월 25일 14:00
감지된 SKILL.md 언어
영어
스타
6
포크
3

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

파일 탐색기
15 개 파일

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
azd-patterns
description
Tips and patterns for Azure Developer CLI (azd) workflows, ACA job deployment, and infrastructure scripting conventions. USE FOR: azd hooks, postdeploy, postprovision, ACA job deployment, container app job image update, publish_aca, deploy_job, azure.yaml hooks, cross-platform deployment scripts, uv run, azd env, azd conventions, infra scripts, MCAPS subscription, SecurityControl Ignore tag, resource group tagging, Defender for Cloud noise, Azure Policy auto-remediation, AZURE_TAGS, pilot posture, demo subscription tagging, AcrPull, ImagePullError, JSON array param, BCP186, FetchingKeyVaultSecretFailed, dependsOn rbac, image-pull credential, ACA placeholder image, azd env set triple-escape. DO NOT USE FOR: az login, tenant switching, subscription isolation (use azure-tenant-isolation), Foundry agents (use microsoft-foundry).
metadata
{"version":"1.6.1"}
# AZD Tips & Patterns Conventions and best practices for `azd` workflows, hooks, and ACA job deployments gathered from real repos. --- ## ACA Job Deployment `azd` does not natively deploy Container Apps **Jobs** — only Container Apps. Jobs need a separate deployment step. ### Recommended: Python + `postdeploy` hook (cross-platform) Used in newer repos. The pattern: 1. **`azure.yaml`** — wire a `postdeploy` hook that runs after `azd deploy`: ```yaml hooks: postdeploy: shell: pwsh run: 'cd infra/scripts && uv sync --frozen && uv run deploy_job.py' interactive: false continueOnError: false ``` 2. **`infra/scripts/deploy_job.py`** — update the job image via Azure SDK: ```python import asyncio, os, sys, logging from utils import load_azd_env from azure.identity.aio import AzureCliCredential from azure.mgmt.appcontainers.aio import ContainerAppsAPIClient async def main(): load_azd_env() image_name = os.getenv("SERVICE_AGENTS_IMAGE_NAME") job_name = os.getenv("JOB_NAME") resource_group = os.getenv("RESOURCE_GROUP") subscription_id = os.getenv("SUBSCRIPTION_ID") tenant_id = os.getenv("AZURE_TENANT_ID") async with AzureCliCredential(tenant_id=tenant_id) as credential: async with ContainerAppsAPIClient(credential, subscription_id) as client: job = await client.jobs.get(resource_group, job_name) job.template.containers[0].image = image_name # Remove stale sidecars — they cause "Failed" status if they exit non-zero job.template.containers = [job.template.containers[0]] poller = await client.jobs.begin_create_or_update(resource_group, job_name, job) await poller.result() if __name__ == "__main__": try: asyncio.run(main()) except Exception as e: logging.critical("Error deploying job: %s", e) sys.exit(1) ``` 3. **`infra/scripts/utils.py`** — load azd environment variables: ```python import json, os, subprocess from pathlib import Path from dotenv import load_dotenv def _ensure_azd_config_dir(): """Walk up from script dir to find repo-root .azure directory. Needed when azd hooks run from a temp directory.""" if os.environ.get("AZD_CONFIG_DIR"): return search = Path(__file__).resolve().parent for _ in range(10): candidate = search / ".azure" if candidate.is_dir() and any( (candidate / d / ".env").exists() for d in os.listdir(candidate) if (candidate / d).is_dir() ): os.environ["AZD_CONFIG_DIR"] = str(candidate) return if search.parent == search: break search = search.parent def load_azd_env(): """Load the default azd environment's .env file.""" _ensure_azd_config_dir() result = subprocess.run("azd env list -o json", shell=True, capture_output=True, text=True) if result.returncode != 0: raise Exception("Error loading azd env") env_json = json.loads(result.stdout) for entry in env_json: if entry["IsDefault"]: load_dotenv(entry["DotEnvPath"], override=True) return raise Exception("No default azd env file found") ``` 4. **`infra/scripts/pyproject.toml`** — managed by `uv`: ```toml [project] name = "infra-scripts" requires-python = ">=3.11" dependencies = [ "azure-identity", "azure-mgmt-appcontainers", "python-dotenv", ] ``` ### Benefits over the old PowerShell approach | Old (`publish_aca.ps1`) | New (`deploy_job.py`) | |---|---| | Shells out to `az acr build` + `az containerapp job update` | Uses Azure SDK directly | | Windows-only (or requires PS Core) | Cross-platform (Python) | | Depends on `az` CLI subscription state | Uses `AzureCliCredential` (respects `AZURE_CONFIG_DIR`) | | No sidecar cleanup | Removes stale sidecars that cause false "Failed" status | | Manual image tagging | Reads image name from azd env vars (`SERVICE_*_IMAGE_NAME`) | ### Legacy: PowerShell script (`publish_aca.ps1`) Older repos (e.g. `acme-demo-form`) use a `postprovision` hook with a PowerShell script: ```yaml # azure.yaml hooks: postprovision: shell: pwsh run: | $RG = azd env get-value AZURE_RESOURCE_GROUP $APP_NAME = azd env get-value AZURE_CAJOB_NAME .\infra\scripts\publish_aca.ps1 -RG $RG -APP_SOURCE_FOLDER .\src\ca-job -APP_NAME $APP_NAME -TYPE job interactive: true continueOnError: false ``` The script does `az acr build` + `az containerapp job update`. This works but is not cross-platform and depends on `az` CLI subscription state. > **⚠️ Gotcha:** `postprovision` runs only on `azd provision`/`azd up`, NOT on `azd deploy`. If the job needs updating on every deploy, use `postdeploy` instead. --- ## Fetch-Latest-Image Pattern (Bicep + ACR) When Bicep provisions a Container App (or Job), it needs an image reference. On **first deploy** the real image hasn't been built yet, so Bicep uses a placeholder. On **subsequent deploys**, `azd deploy` builds the real image into ACR and patches the running container. This is the expected lifecycle — the placeholder in Bicep is intentional, not a bug. ### Why the placeholder exists ```bicep // infra/main.bicep — first-deploy bootstrap resource mcpApp 'Microsoft.App/containerApps@2024-10-02-preview' = { ... tags: { 'azd-service-name': 'mcp' // REQUIRED — azd deploy uses this tag to find the resource } properties: { template: { containers: [ { name: 'mcp' // Placeholder: azd deploy will swap this to the real ACR image image: 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest' } ] } } } ``` > ⚠️ **The `azd-service-name` tag is MANDATORY.** `azd deploy` locates the > Container App to patch by searching for a resource tagged with > `azd-service-name: <service>` matching the service name in `azure.yaml`. > Without this tag, `azd deploy` fails with "resource not found: unable to > find a resource tagged with 'azd-service-name: mcp'". This is not > documented prominently in the azd docs — add it to every ACA Bicep. On `azd up`: 1. `azd provision` runs Bicep → Container App created with placeholder image 2. `azd deploy` builds `src/mcp/` → pushes to ACR → patches the Container App with the real image 3. Subsequent `azd deploy` calls repeat step 2 only ### Pattern A — `azure.yaml` service binding (recommended) Let `azd` manage the image lifecycle. Declare each container as a service: ```yaml # azure.yaml services: mcp: host: containerapp project: src/mcp docker: path: src/mcp/Dockerfile context: src/mcp ``` `azd deploy` will: - Build the Dockerfile into ACR (tag = `azd-deploy-{timestamp}`) - Set env var `SERVICE_MCP_IMAGE_NAME` = full ACR image ref - Patch the Container App to use the new image ### `azure.yaml` field traps > ⚠️ **`language: html` and `language: static` are NOT valid `azure.yaml` > values.** They look plausible for static or front-end services but silently > break `azd` build detection. For custom-Dockerfile services, omit > `language` entirely and declare only `project` + `docker`. When the Dockerfile is not in the service root, set `docker.context` to the actual build-context directory: ```yaml services: web: host: containerapp project: . docker: path: ./src/Dockerfile context: ./src ``` Without `docker.context`, `azd deploy` builds from the service root and the Dockerfile's `COPY` lines fail because the expected files are outside the build context. ### Pattern B — `postdeploy` hook with `SERVICE_*_IMAGE_NAME` For Container Apps **Jobs** (not managed by `azd deploy`), use a postdeploy hook that reads the azd-generated image name: ```yaml hooks: postdeploy: shell: pwsh run: 'cd infra/scripts && uv sync --frozen && uv run deploy_job.py' ``` ```python # infra/scripts/deploy_job.py image_name = os.getenv("SERVICE_MCP_IMAGE_NAME") # set by azd deploy # ... patch the Container App Job with this image ``` ### Pattern C — Bicep `containerImage` parameter with default For repos where the image is pre-built (e.g., shared ACR across teams): ```bicep param containerImage string = 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest' resource app 'Microsoft.App/containerApps@2024-10-02-preview' = { ... properties: { template: { containers: [{ name: 'app', image: containerImage }] } } } ``` Override at deploy time: `azd provision --parameter containerImage=myacr.azurecr.io/app:v2` ### The `SERVICE_*_IMAGE_NAME` convention `azd` auto-generates env vars for each service declared in `azure.yaml`: | azure.yaml service name | Env var | Example value | |---|---|---| | `mcp` | `SERVICE_MCP_IMAGE_NAME` | `acrfoo.azurecr.io/mcp:azd-deploy-1716300000` | | `agent` | `SERVICE_AGENT_IMAGE_NAME` | `acrfoo.azurecr.io/agent:azd-deploy-1716300000` | These are available in `postdeploy` hooks and in `.azure/{env}/.env`. > **⚠️ Don't treat the Bicep placeholder as a bug.** When reviewing Bicep > that uses `containerapps-helloworld:latest`, check whether `azure.yaml` > declares the corresponding service. If it does, `azd deploy` handles the > image swap automatically. The placeholder is a bootstrap artifact, not a > deployment gap. --- ## ACA Job: silent-failure debug playbook **Symptom we keep hitting.** ACA Job execution flips to `Failed` within 60s of the deploy completing, but **zero console + system logs reach Log Analytics Workspace** even though sibling ACA apps (MCP server, agent) in the same env route logs cleanly. `az containerapp job logs show` hangs indefinitely. This has bitten deadline-watcher cron jobs after in-process refactors and is often the biggest end-of-session blocker. The diagnostic ladder below goes **cheap → expensive**. Stop at the first rung that surfaces actionable signal. ### Rung 1 — Verify the basics aren't lying ```powershell $RG = 'rg-<your-process>-poc' $JOB = 'aca-job-deadline-watcher' # Confirm the job exists and what image it's actually running az containerapp job show --resource-group $RG --name $JOB ` --query "{state:properties.runningState, image:properties.template.containers[0].image, env:properties.environmentId}" -o table # Confirm last execution(s) az containerapp job execution list --resource-group $RG --name $JOB ` --query "[].{name:name, status:properties.status, start:properties.startTime, end:properties.endTime}" ` --output table | Select-Object -First 10 ``` Common findings: - Image is `mcr.microsoft.com/azuredocs/containerapps-helloworld` — `azd provision` ran but `azd deploy` / publish_aca didn't (placeholder leak — `threadlight-safe-check` Step 3.5 (image-probe) catches this; check separately). - Image hash matches latest ACR push but jobs still Failed → real runtime crash, advance to Rung 2. ### Rung 2 — Probe LAW for ANY signal at all (including system events) ```powershell $WS = az monitor log-analytics workspace list --resource-group $RG ` --query "[0].customerId" -o tsv $JOB = 'aca-job-deadline-watcher' # Console logs — what stdout/stderr emitted (this is the "no logs" thing if empty) az monitor log-analytics query --workspace $WS --analytics-query @" ContainerAppConsoleLogs_CL | where ContainerJobName_s == '$JOB' | order by TimeGenerated desc | project TimeGenerated, RevisionName_s, Log_s | take 50 "@ -o table # System logs — ACA control plane events (start/stop/pull/probe failures) az monitor log-analytics query --workspace $WS --analytics-query @" ContainerAppSystemLogs_CL | where ContainerAppName_s == '$JOB' | order by TimeGenerated desc | project TimeGenerated, Type_s, Reason_s, Log_s | take 50 "@ -o table ``` What each empty/non-empty combo means: | ConsoleLogs | SystemLogs | Likely cause | |---|---|---| | Empty | Empty | LAW routing not wired to this job (env-level diag setting missing or job created before diag setting), OR ACA Job log-pipeline lag (try again 2-3 min after execution) | | Empty | Non-empty | Image pulls / starts but exits before any `print()` / `logging.info()` reaches LAW. Usually an **import-time crash** — the process exits before `logging.basicConfig()` fires. Move to Rung 3. | | Non-empty | Non-empty | You have signal — read it. | ### Rung 3 — Force first-line logging in the entrypoint When the ConsoleLogs pipe is empty but SystemLogs show the container started + exited, the crash is import-side. Tactic: emit a synchronous write to stderr **before any other import** so even an `ImportError` produces a visible breadcrumb. ```python # main.py — top of file, BEFORE any project imports import sys sys.stderr.write("BOOT: entrypoint reached\n"); sys.stderr.flush() try: import azure.identity # the usual suspects first sys.stderr.write("BOOT: azure.identity OK\n"); sys.stderr.flush() import azure.cosmos sys.stderr.write("BOOT: azure.cosmos OK\n"); sys.stderr.flush() # ... your imports ... except Exception as e: sys.stderr.write(f"BOOT: import failed: {type(e).__name__}: {e}\n") sys.stderr.flush() raise ``` Push, redeploy, kick off a manual execution (`az containerapp job start --resource-group $RG --name $JOB`), wait ~90s, re-run the ConsoleLogs query at Rung 2. The breadcrumbs reveal which import exploded. ### Rung 4 — Activity log probe (orthogonal channel — bypasses LAW entirely) ```powershell
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기