Azure Developer CLI (azd)
Azure Developer CLI (azd) is the default and required deployment tool for this repo.
It provides cross-platform environment management, lifecycle hooks, and CI/CD
pipeline generation.
Quick Decision
Section titled “Quick Decision”| Scenario | Use | Why |
|---|---|---|
| New Bicep project | azd | Default, cross-platform, built-in env management |
| New Terraform project | azd | infra.provider: terraform gives TF + azd simplicity |
Existing project with azure.yaml | azd | Already configured |
Existing project without azure.yaml | azd (generate azure.yaml) | Generate azure.yaml via azure-prepare, then use azd |
| Need fine-grained phased deployment | azd with hooks | Use preprovision/postprovision hooks for phased logic |
| CI/CD pipeline (non-interactive) | azd | azd provision --no-prompt with env vars |
Comparison
Section titled “Comparison”| Factor | azd | deploy.ps1 |
|---|---|---|
| Cross-platform | Linux, macOS, Windows | PowerShell only |
| Environment management | Built-in (azd env new/set/list) | Manual parameters |
| Hooks (pre/post deploy) | azure.yaml hooks | Custom script logic |
| Phased deployment | Use hooks (preprovision/postprovision) | Fine-grained phases (deprecated) |
| Preview / what-if | azd provision --preview | deploy.ps1 -WhatIf |
| IaC providers | Bicep or Terraform | Bicep only |
| Secret management | azd env set-secret (Key Vault) | Manual parameters |
| CI/CD generation | azd pipeline config | Manual authoring |
| Service deployment | azd deploy (app code) | Not supported (infra only) |
| Official docs | Azure Developer CLI docs | N/A (custom script) |
Per-Project Convention
Section titled “Per-Project Convention”This repo supports multiple independent projects. Each project is a fully
self-contained azd project — azure.yaml and .azure/ live inside the
IaC project directory, never at the repo root.
infra/{iac}/{project}/├── azure.yaml # azd manifest (infra.path: .)├── .azure/ # git-ignored; per-environment state│ ├── plan.md # azure-prepare output — source of truth│ └── {project}-{env}/ # e.g., hub-spoke-dev/│ └── .env # azd environment variables├── main.bicep (or main.tf) # IaC entry point (co-located)├── deploy.ps1 # DEPRECATED — legacy fallback only└── modules/Key rules:
- Environment names use
{project}-{env}(e.g.,hub-spoke-dev) to avoid collisions .azure/folders are git-ignored (contains subscription IDs and env-specific state)- Run azd from the project dir:
cd infra/{iac}/{project}thenazdcommands - Or use the
-Cflag from repo root:azd -C infra/{iac}/{project} env list
azd Workflow
Section titled “azd Workflow”The full deployment lifecycle follows three agent-driven steps:
1. Prepare
Section titled “1. Prepare”The azure-prepare skill generates infrastructure code and the deployment plan.
# Outputs: azure.yaml, main.bicep/main.tf, modules/, .azure/plan.md2. Validate
Section titled “2. Validate”The azure-validate skill runs pre-deployment checks.
cd infra/{iac}/{project}
# Bicepazd provision --preview
# Terraformazd provision --preview# or: terraform validate + terraform plan3. Deploy
Section titled “3. Deploy”The azure-deploy skill executes the deployment.
cd infra/{iac}/{project}
# Create environmentazd env new {project}-{env}azd env set AZURE_LOCATION swedencentral
# Full provision + deployazd up --no-prompt
# Or infrastructure onlyazd provisionEnvironment Preflight
Section titled “Environment Preflight”Before azd provision --no-prompt, verify required values are set:
azd env get-values# Must have: AZURE_SUBSCRIPTION_ID, AZURE_LOCATION, AZURE_ENV_NAME
# If missing:azd env set AZURE_SUBSCRIPTION_ID "$(az account show --query id -o tsv)"azd env set AZURE_LOCATION swedencentraldeploy.ps1 Workflow (Deprecated)
Section titled “deploy.ps1 Workflow (Deprecated)”Use only for legacy projects that have not yet adopted azure.yaml.
Single Deployment
Section titled “Single Deployment”cd infra/bicep/{project}pwsh deploy.ps1 -WhatIf # Previewpwsh deploy.ps1 # Deploy allPhased Deployment
Section titled “Phased Deployment”Deploy each phase sequentially with approval gates:
pwsh deploy.ps1 -Phase Foundation -WhatIf # Previewpwsh deploy.ps1 -Phase Foundation # Deploy# Repeat for: Security, Data, Compute, Edge| Phase | Resources | When to Use |
|---|---|---|
| Foundation | Resource group, networking, Key Vault | Always first |
| Security | Identity, RBAC, certificates | After networking |
| Data | Storage, databases, messaging | After security |
| Compute | App Service, Functions, containers | After data layer |
| Edge | CDN, Front Door, DNS | After compute |
azd Hooks
Section titled “azd Hooks”Hooks replace custom pre/post logic that deploy.ps1 handles in-script.
Define them in azure.yaml:
hooks: preprovision: posix: shell: sh run: ./scripts/pre-provision.sh windows: shell: pwsh run: ./scripts/pre-provision.ps1 postprovision: posix: shell: sh run: ./scripts/post-provision.sh windows: shell: pwsh run: ./scripts/post-provision.ps1| Hook | Purpose | Example |
|---|---|---|
preprovision | Auth validation | az account get-access-token --output none |
preprovision | Prerequisite check | Verify quota, check policy compliance |
postprovision | Resource verification | Query Azure Resource Graph |
postprovision | RBAC assignment | Assign roles (use || true for idempotency) |
postprovision | SQL setup | Run EF migrations, configure managed identity |
azure.yaml Schema
Section titled “azure.yaml Schema”Key fields for the co-located layout:
name: {project}metadata: template: {project}@1.0.0infra: provider: bicep # or: terraform path: . # co-located with azure.yaml module: main # entry point (main.bicep or main.tf)services: # optional — app code deployment web: project: ./src/web language: js host: containerapphooks: # optional — lifecycle hooks preprovision: ... postprovision: ...Troubleshooting
Section titled “Troubleshooting”| Error | Cause | Fix |
|---|---|---|
azure.yaml not found | Not in project directory | cd infra/{iac}/{project} first |
missing required inputs | Bicep params not mapped | azd env config set infra.parameters.<param> <value> |
main.tfvars.json not found | TF param file missing | Create main.tfvars.json with ${AZURE_*} mappings |
environment not found | No azd env created | azd env new {project}-{env} |
.azure/ at repo root | Breaks multi-project | Move to infra/{iac}/{project}/.azure/ |
| RBAC “already exists” | Idempotent runs | Add || true to role assignment commands in hooks |
| Preview fails despite login | az vs azd auth mismatch | Both az and azd need separate auth sessions |