Skip to content

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.

ScenarioUseWhy
New Bicep projectazdDefault, cross-platform, built-in env management
New Terraform projectazdinfra.provider: terraform gives TF + azd simplicity
Existing project with azure.yamlazdAlready configured
Existing project without azure.yamlazd (generate azure.yaml)Generate azure.yaml via azure-prepare, then use azd
Need fine-grained phased deploymentazd with hooksUse preprovision/postprovision hooks for phased logic
CI/CD pipeline (non-interactive)azdazd provision --no-prompt with env vars
Factorazddeploy.ps1
Cross-platformLinux, macOS, WindowsPowerShell only
Environment managementBuilt-in (azd env new/set/list)Manual parameters
Hooks (pre/post deploy)azure.yaml hooksCustom script logic
Phased deploymentUse hooks (preprovision/postprovision)Fine-grained phases (deprecated)
Preview / what-ifazd provision --previewdeploy.ps1 -WhatIf
IaC providersBicep or TerraformBicep only
Secret managementazd env set-secret (Key Vault)Manual parameters
CI/CD generationazd pipeline configManual authoring
Service deploymentazd deploy (app code)Not supported (infra only)
Official docsAzure Developer CLI docsN/A (custom script)

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} then azd commands
  • Or use the -C flag from repo root: azd -C infra/{iac}/{project} env list

The full deployment lifecycle follows three agent-driven steps:

The azure-prepare skill generates infrastructure code and the deployment plan.

Terminal window
# Outputs: azure.yaml, main.bicep/main.tf, modules/, .azure/plan.md

The azure-validate skill runs pre-deployment checks.

Terminal window
cd infra/{iac}/{project}
# Bicep
azd provision --preview
# Terraform
azd provision --preview
# or: terraform validate + terraform plan

The azure-deploy skill executes the deployment.

Terminal window
cd infra/{iac}/{project}
# Create environment
azd env new {project}-{env}
azd env set AZURE_LOCATION swedencentral
# Full provision + deploy
azd up --no-prompt
# Or infrastructure only
azd provision

Before azd provision --no-prompt, verify required values are set:

Terminal window
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 swedencentral

Use only for legacy projects that have not yet adopted azure.yaml.

Terminal window
cd infra/bicep/{project}
pwsh deploy.ps1 -WhatIf # Preview
pwsh deploy.ps1 # Deploy all

Deploy each phase sequentially with approval gates:

Terminal window
pwsh deploy.ps1 -Phase Foundation -WhatIf # Preview
pwsh deploy.ps1 -Phase Foundation # Deploy
# Repeat for: Security, Data, Compute, Edge
PhaseResourcesWhen to Use
FoundationResource group, networking, Key VaultAlways first
SecurityIdentity, RBAC, certificatesAfter networking
DataStorage, databases, messagingAfter security
ComputeApp Service, Functions, containersAfter data layer
EdgeCDN, Front Door, DNSAfter compute

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
HookPurposeExample
preprovisionAuth validationaz account get-access-token --output none
preprovisionPrerequisite checkVerify quota, check policy compliance
postprovisionResource verificationQuery Azure Resource Graph
postprovisionRBAC assignmentAssign roles (use || true for idempotency)
postprovisionSQL setupRun EF migrations, configure managed identity

Key fields for the co-located layout:

name: {project}
metadata:
template: {project}@1.0.0
infra:
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: containerapp
hooks: # optional — lifecycle hooks
preprovision: ...
postprovision: ...

ErrorCauseFix
azure.yaml not foundNot in project directorycd infra/{iac}/{project} first
missing required inputsBicep params not mappedazd env config set infra.parameters.<param> <value>
main.tfvars.json not foundTF param file missingCreate main.tfvars.json with ${AZURE_*} mappings
environment not foundNo azd env createdazd env new {project}-{env}
.azure/ at repo rootBreaks multi-projectMove to infra/{iac}/{project}/.azure/
RBAC “already exists”Idempotent runsAdd || true to role assignment commands in hooks
Preview fails despite loginaz vs azd auth mismatchBoth az and azd need separate auth sessions