Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Deploy applications and infrastructure to Azure using Copilot-guided workflows and Azure MCP
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/pre-deploy-checklist.md
1# Pre-Deployment Checklist23> **CRITICAL**: Before running ANY provisioning commands, you MUST complete this checklist IN ORDER.4>5> ⛔ **DO NOT** run `azd up` until ALL steps are complete. Trial-and-error wastes time and creates orphan resources.67## Step 1: Check Current Subscription89Use the Azure MCP tool to get current subscription:1011```12mcp_azure_mcp_subscription_list13```1415**CLI fallback:**16```bash17az account show --query "{name:name, id:id}" -o json18```1920## Step 2: Prompt User for Subscription2122**You MUST use `ask_user`** to confirm the subscription. Find the default subscription (marked `isDefault: true`) from Step 1 results and present it as the recommended choice.2324✅ **Correct — show actual name and ID as a choice:**25```26ask_user(27question: "Which Azure subscription would you like to deploy to?",28choices: [29"Use current: <subscription-name> (<subscription-id>) (Recommended)",30"Let me specify a different subscription"31]32)33```3435❌ **Wrong — never use freeform input for subscription:**36```37ask_user(38question: "Which Azure subscription should I deploy to? I'll need the subscription name or ID."39)40```4142## Step 3: Create AZD Environment FIRST4344> ⚠️ **MANDATORY** — Create the environment BEFORE setting any variables or running `azd up`.45>46> ⛔ **DO NOT** manually create `.azure/` folder with `mkdir` or `New-Item`. Let `azd` create it.4748**For new projects (no azure.yaml):**49```bash50azd init -e <environment-name> --no-prompt51```5253**For existing projects (azure.yaml exists):**54```bash55azd env new <environment-name> --no-prompt56```5758Both commands create:59- `.azure/<env-name>/` folder with config files60- Set the environment as default6162The environment name becomes part of the resource group name (`rg-<env-name>`).6364## Step 4: Check if Resource Group Already Exists6566> ⛔ **CRITICAL** — Skip this and you'll hit "Invalid resource group location" errors.6768Use the Azure MCP tool to list resource groups:6970```71mcp_azure_mcp_group_list72subscription: <subscription-id>73```7475Then check if `rg-<env-name>` exists in the results.7677**CLI fallback:**78```bash79az group show --name rg-<env-name> --query "{location:location}" -o json 2>&180```8182**If RG exists:**83- Use `ask_user` to offer choices:841. Use existing RG location (show the location)852. Choose a different environment name863. Delete the existing RG and start fresh8788**If RG doesn't exist:** Proceed to location selection.8990## Step 5: Check for Tag Conflicts (AZD only)9192> ⚠️ AZD uses `azd-service-name` tags to find deployment targets **within the target resource group**. Multiple resources with the same tag in the same RG cause failures. Tags in other RGs are fine.9394```bash95az resource list --resource-group rg-<env-name> --tag azd-service-name=<service-name> --query "[].name" -o table96```9798Check for each service in `azure.yaml`. If duplicates exist **in the target RG**:991001. **Preferred — Fresh environment**: Run `azd env new <new-name> --no-prompt` and restart from Step 4. Non-destructive, no user confirmation needed, avoids orphan risks.1012. **Alternative — Delete conflicts**: Use `ask_user` to confirm deletion of old resources (required by global rules).102103## Step 5a: Check for Existing Container Apps Environments (Container Apps only)104105> ⛔ **MANDATORY for Container Apps deployments** — Skip this and `azd up` may silently create a new Container Apps environment with an unexpected name (e.g. `"deployment-prod"`), causing a much longer deployment and environment drift.106107**Only run this step if the resource group `rg-<env-name>` already exists (confirmed in Step 4).** If the resource group does not exist yet, skip to Step 6.108109If `azure.yaml` includes a Container Apps service and the resource group exists, check for existing Container Apps environments **before** running `azd up`:110111```bash112az containerapp env list \113--resource-group rg-<env-name> \114--query "[].{name:name, location:location, provisioningState:properties.provisioningState}" \115-o table116```117118**PowerShell:**119```powershell120az containerapp env list `121--resource-group rg-<env-name> `122--query "[].{name:name, location:location, provisioningState:properties.provisioningState}" `123-o table124```125126**If no existing environments are found:** No action needed — proceed to Step 6.127128**If existing environments are found:** Check the `provisioningState` column in the output. Environments with a state of `Failed` or `Deleting` are not usable — treat them the same as no conflict (proceed to Step 6), or use option 3 below to delete the stuck environment first.129130For environments with a `provisioningState` of `Succeeded`, use `ask_user` to present the conflict and offer choices:131132```133ask_user(134question: "I found existing Container Apps environment(s) in rg-<env-name>:135<environment-list>136Proceeding without resolving this conflict may cause azd to create an additional environment.137How would you like to proceed?",138choices: [139"Use the existing environment — select the matching AZD environment (Recommended)",140"Choose a different AZD environment name to deploy to a new resource group",141"Delete the existing Container Apps environment and start fresh (DESTRUCTIVE)"142]143)144```145146**Resolution per choice:**1471481. **Use existing environment** — First check if the matching AZD environment exists locally:149```bash150azd env list151```152- **If the environment exists locally**, select it:153```bash154azd env select <matching-env-name>155```156- **If the environment does NOT exist locally** (e.g., it was provisioned on a different machine or has been cleaned up), create it and configure it to target the existing resource group:157```bash158azd env new <matching-env-name> --no-prompt159azd env set AZURE_SUBSCRIPTION_ID <subscription-id>160azd env set AZURE_LOCATION <location-of-existing-rg>161```1621632. **Choose a different name** — Create a new AZD environment:164```bash165azd env new <new-unique-env-name> --no-prompt166azd env set AZURE_SUBSCRIPTION_ID <subscription-id>167# Then restart from Step 4 with the new environment name168```1691703. **Delete and start fresh** — Delete the conflicting environment (requires `ask_user` confirmation per global-rules):171```bash172az containerapp env delete \173--name <environment-name> \174--resource-group rg-<env-name> \175--yes176```177178**PowerShell:**179```powershell180az containerapp env delete `181--name <environment-name> `182--resource-group rg-<env-name> `183--yes184```185186## Step 6: Prompt User for Location187188**You MUST use `ask_user`** with regions that support ALL services in the architecture.189190See [Region Availability](region-availability.md) for service-specific limitations.191192## Step 7: Set Environment Variables193194> ⚠️ **Set ALL variables BEFORE running `azd up`** — not during error recovery.195196Environment should already be configured during **azure-validate**. Run `azd env get-values` to confirm.197198Verify settings:199```bash200azd env get-values201```202203## Step 8: Only NOW Run Deployment204205```bash206azd up --no-prompt207```208209---210211## Step 9: Verify Terraform Variable Resolution (AZD+Terraform Only)212213> ⚠️ **MANDATORY for azd+Terraform projects.** Skip this step for Bicep or pure Terraform deployments.214215Before running `azd up`, verify no Go-style template variables exist in Terraform files:216217```bash218# Fail if Go-style template variables found in Terraform files219if grep -rn '{{ *\.Env\.' infra/ --include='*.tf' --include='*.tfvars.json'; then220echo "ERROR: Unresolved Go-style template variables found"221exit 1222fi223224# Check main.tfvars.json uses correct ${VAR} syntax (not Go-style templates)225if test -f infra/main.tfvars.json; then226if grep -q '{{ *\.Env\.' infra/main.tfvars.json; then227echo "ERROR: main.tfvars.json uses Go-style templates. Use \${VAR} syntax instead."228exit 1229fi230fi231```232233**If either check fails:**2341. Fix `main.tfvars.json` syntax: replace `{{ .Env.VAR }}` with `${VAR}` (e.g., `${AZURE_ENV_NAME}`)2352. For variables not in `main.tfvars.json`, use `TF_VAR_*` environment variables2363. Re-run `azure-validate` before proceeding237238---239240## Quick Reference: Correct AZD Sequence241242```bash243# 1. Create environment FIRST244azd env new myapp-dev --no-prompt245246# 2. Set subscription247azd env set AZURE_SUBSCRIPTION_ID 25fd0362-...248249# 3. Set location (after checking RG doesn't conflict)250azd env set AZURE_LOCATION westus2251252# 4. Verify253azd env get-values254255# 5. Deploy256azd up --no-prompt257```258259## Common Mistakes to Avoid260261| ❌ Wrong | ✅ Correct |262|----------|-----------|263| `azd up --location eastus2` | `azd env set AZURE_LOCATION eastus2` then `azd up` |264| Running `azd up` without environment | `azd env new <name> --no-prompt` first |265| Assuming location without checking RG | Check `az group show` before choosing |266| Ignoring tag conflicts in target RG | Check `az resource list --resource-group rg-<env-name>` before deploy |267| Skipping Container Apps environment check | Run `az containerapp env list --resource-group rg-<env-name>` before deploy (Step 5a) |268269---270271## Service-Specific Checks272273### Container Apps + ACR — Pre-Deploy RBAC Health Check274275> **⛔ MANDATORY**: If the plan includes Container Apps that pull images from ACR using a managed identity, you **MUST** use a two-phase flow with an `AcrPull` RBAC propagation gate **between** provisioning and image deployment. Skipping the gate causes the Container App revision to time out (~900 seconds) waiting for image pull permission — a known Azure RBAC propagation delay.276>277> The exact phase-1/phase-2 commands depend on whether the infra is **Bicep** (managed by `azd`) or **Terraform** (managed by `terraform`). Pick the matching path below.278279This check is **required** when ALL of the following are true:280- The plan includes a Container App service that pulls images from ACR281- The infra (Bicep or Terraform) assigns an `AcrPull` role for the Container App's managed identity on ACR using the two-phase deployment pattern282- Phase 1 has just completed and the real application image has not yet been pushed/deployed283284> 📦 **Placeholder image:** Both paths use `mcr.microsoft.com/azuredocs/containerapps-helloworld:latest` as the public phase-1 placeholder so the Container App can be provisioned before the real image exists in ACR.285286#### Path A — Bicep (AZD)287288> 💡 **Two-phase Bicep pattern:** `azd provision` succeeds immediately because the Container App is provisioned with a public placeholder image (not an ACR image). The `AcrPull` role assignment is deployed in a separate module with no circular dependency. `azd deploy` then configures the registry/identity link (the equivalent CLI step is `az containerapp registry set --name <app-name> --resource-group rg-<env-name> --server <acr-login-server> --identity system`) and pushes the real image via the Azure API — but the `AcrPull` role still needs time to propagate before this succeeds.289290> ⛔ **Do not use `azd up` for this scenario.** `azd up` combines provisioning and deployment and skips the propagation gate.291292**Required flow:**2931. Run `azd provision`2942. Complete the RBAC health check (Steps A–C below)2953. Run `azd deploy --no-prompt`296297#### Path B — Terraform (CLI)298299> 💡 **Two-phase Terraform pattern:** `terraform apply` succeeds immediately because the Container App is provisioned with a public placeholder image (not an ACR image) and **no `registry` block**. The `AcrPull` role assignment is a separate `azurerm_role_assignment` resource that depends on the Container App's system-assigned identity, so there is no circular dependency. The post-apply CLI step then builds and pushes the real image with `az acr build`, configures the registry/identity link with `az containerapp registry set --name <app-name> --resource-group rg-<env-name> --server <acr-login-server> --identity system`, and switches the revision to the real image with `az containerapp update --image <acr-login-server>/<image>:<tag>` — but the `AcrPull` role still needs time to propagate before these succeed.300301**Required flow:**3021. Run `terraform apply` (provisions ACR, Container App with placeholder image, and `AcrPull` role assignment)3032. Complete the RBAC health check (Steps A–C below)3043. Build, push, and switch to the real image:305```bash306az acr build --registry <acr-name> --image <image>:<tag> ./src/<service>307az containerapp registry set \308--name <app-name> \309--resource-group rg-<env-name> \310--server <acr-login-server> \311--identity system312az containerapp update \313--name <app-name> \314--resource-group rg-<env-name> \315--image <acr-login-server>/<image>:<tag>316```317318**PowerShell:**319```powershell320az acr build --registry <acr-name> --image <image>:<tag> ./src/<service>321az containerapp registry set `322--name <app-name> `323--resource-group rg-<env-name> `324--server <acr-login-server> `325--identity system326az containerapp update `327--name <app-name> `328--resource-group rg-<env-name> `329--image "<acr-login-server>/<image>:<tag>"330```331332#### RBAC Health Check (Both Paths)333334The following Steps A–C are identical for Bicep (AZD) and Terraform.335336**Step A — Get the Container App's managed identity principal ID:**337338```bash339PRINCIPAL_ID=$(az containerapp identity show \340--name <app-name> \341--resource-group rg-<env-name> \342--query principalId -o tsv)343```344345**PowerShell:**346```powershell347$PrincipalId = az containerapp identity show `348--name <app-name> `349--resource-group rg-<env-name> `350--query principalId -o tsv351```352353**Step B — Get the ACR resource ID:**354355```bash356ACR_ID=$(az acr show \357--name <acr-name> \358--resource-group rg-<env-name> \359--query id -o tsv)360```361362**PowerShell:**363```powershell364$AcrId = az acr show `365--name <acr-name> `366--resource-group rg-<env-name> `367--query id -o tsv368```369370**Step C — Poll until the `AcrPull` role is visible (up to 5 minutes):**371372```bash373for attempt in 1 2 3 4 5; do374ROLE=$(az role assignment list \375--scope "$ACR_ID" \376--assignee-object-id "$PRINCIPAL_ID" \377--query "[?roleDefinitionName=='AcrPull'].roleDefinitionName" \378-o tsv 2>/dev/null)379380if printf '%s\n' "$ROLE" | grep -qx 'AcrPull'; then381echo "AcrPull role confirmed. Proceeding with azd deploy."382break383fi384385if [ "$attempt" -eq 5 ]; then386echo "AcrPull role not found after 5 minutes. Assign it manually before retrying."387exit 1388fi389390echo "Waiting for AcrPull RBAC propagation (attempt $attempt/5, waiting 60s)..."391sleep 60392done393```394395**PowerShell:**396```powershell397for ($attempt = 1; $attempt -le 5; $attempt++) {398$Role = az role assignment list `399--scope $AcrId `400--assignee-object-id $PrincipalId `401--query "[?roleDefinitionName=='AcrPull'].roleDefinitionName" `402-o tsv 2>$null403404if ($Role -eq "AcrPull") {405Write-Output "AcrPull role confirmed. Proceeding with azd deploy."406break407}408409if ($attempt -eq 5) {410Write-Output "AcrPull role not found after 5 minutes. Assign it manually before retrying."411exit 1412}413414Write-Output "Waiting for AcrPull RBAC propagation (attempt $attempt/5, waiting 60s)..."415Start-Sleep -Seconds 60416}417```418419Only after this check confirms `AcrPull` has propagated should you run **Path A** `azd deploy --no-prompt` or **Path B** `az acr build` / `az containerapp registry set` / `az containerapp update`.420421> 💡 **Tip:** If `AcrPull` is missing entirely, assign it manually using the steps in [Container App Revision Timeout](recipes/azd/errors.md#container-app-revision-timeout), then re-run the poll loop above.422423---424425### AZD/Bicep — Non-User Principal RBAC Fix426427> **⛔ MANDATORY**: If the deploying identity is NOT an interactive user (e.g., service principal in CI/CD), you **MUST** check and patch Bicep templates that hardcode user-type RBAC role assignments. Skipping this causes `PrincipalType` mismatch errors during provisioning, leading to costly retry loops and potential test timeouts.428429This check is **required** when ALL of the following are true:430- The deployment uses AZD with Bicep infrastructure431- The deploying identity is not an interactive user (service principal, managed identity, etc.)432- The Bicep templates contain `allowUserIdentityPrincipal` set to `true` or role assignments with `principalType: 'User'`433434> ⚠️ **Known affected templates:** `functions-quickstart-python-http-azd` and other AZD quickstart templates that include optional user-identity RBAC. See [Principal Type Mismatch](recipes/azd/errors.md#principal-type-mismatch) for details.435436**Step A — Detect the deploying identity type:**437438```bash439AUTH_TYPE=$(az account show --query user.type -o tsv)440echo "Auth type: $AUTH_TYPE"441```442443**PowerShell:**444```powershell445$AuthType = az account show --query user.type -o tsv446Write-Output "Auth type: $AuthType"447```448449If `AUTH_TYPE` is `user`, skip to the next section — no fix needed.450451**Step B — Search for user-type RBAC in Bicep templates:**452453```bash454grep -rn "allowUserIdentityPrincipal" infra/ --include='*.bicep'455```456457**PowerShell:**458```powershell459Select-String -Path infra\*.bicep -Pattern 'allowUserIdentityPrincipal' -Recurse460```461462If no matches are found, skip to the next section.463464**Step C — Patch `allowUserIdentityPrincipal` to `false`:**465466For each match where `allowUserIdentityPrincipal` is set to `true`, change it to `false`:467468```bicep469// Before (fails with service principal deployments):470allowUserIdentityPrincipal: true471472// After (CI/CD compatible):473allowUserIdentityPrincipal: false474```475476> ⚠️ **This is a CI/CD compatibility patch.** Setting this to `false` disables optional interactive-user RBAC role assignments. The application's managed identity roles are unaffected. If the user later needs local interactive access, they can re-enable this flag or assign roles manually.477478> ⚠️ **Do NOT** attempt to fix this by clearing `AZURE_PRINCIPAL_ID`. The `azd` CLI repopulates this value from the current auth context on every run.479480---481482### Durable Functions — Verify DTS Backend483484> **⛔ MANDATORY**: If the plan includes Durable Functions, verify infrastructure uses **Durable Task Scheduler** (DTS), NOT Azure Storage.485486Check that `infra/` Bicep files contain:487- `Microsoft.DurableTask/schedulers` resource488- `Microsoft.DurableTask/schedulers/taskHubs` child resource489- `Durable Task Data Contributor` RBAC role assignment490- `DURABLE_TASK_SCHEDULER_CONNECTION_STRING` app setting491492If any are missing, **STOP** and invoke **azure-prepare** to regenerate with the durable recipe.493494---495496## Non-AZD Deployments497498**For Azure CLI / Bicep:**499```bash500az account set --subscription <subscription-id-or-name>501# Pass location as parameter: --location <location>502```503504**For Terraform:**505```bash506az account set --subscription <subscription-id-or-name>507# Set in terraform.tfvars or -var="location=<location>"508```509