Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Prepare Azure environments for new workloads—subscriptions, networking, identity, and landing zones
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/recipes/azd/terraform.md
1# AZD with Terraform23Use Azure Developer CLI (azd) with Terraform as the infrastructure provider.45## When to Use67Choose azd+Terraform when you want:8- **Terraform's multi-cloud capabilities** with **azd's deployment simplicity**9- Existing Terraform expertise but want `azd up` convenience10- Team familiar with Terraform but needs environment management11- Multi-cloud IaC with Azure-first deployment experience1213## Benefits1415| Feature | Pure Terraform | AZD + Terraform |16|---------|---------------|-----------------|17| Deploy command | `terraform apply` | `azd up` |18| Environment management | Manual workspaces | Built-in `azd env` |19| CI/CD generation | Manual setup | Auto-generated pipelines |20| Service deployment | Manual scripts | Automatic from azure.yaml |21| State management | Manual backend setup | Configurable |22| Multi-cloud | ✅ Yes | ✅ Yes |2324## Configuration2526### 1. azure.yaml Structure2728Create `azure.yaml` in project root:2930```yaml31name: myapp32metadata:33template: azd-init3435# Specify Terraform as IaC provider36infra:37provider: terraform38path: ./infra3940# Define services as usual41services:42api:43project: ./src/api44language: python45host: containerapp46docker:47path: ./src/api/Dockerfile4849web:50project: ./src/web51language: js52host: staticwebapp53dist: dist54```5556### 2. Terraform File Structure5758Place Terraform files in `./infra/`:5960```61infra/62├── main.tf # Main resources63├── variables.tf # Variable definitions64├── outputs.tf # Output values65├── provider.tf # Provider configuration66└── modules/67├── api/68│ └── main.tf69└── web/70└── main.tf71```7273### 3. Provider Configuration7475**provider.tf:**76```hcl77terraform {78required_version = ">= 1.5.0"7980required_providers {81azurerm = {82source = "hashicorp/azurerm"83version = "~> 4.2"84}85azurecaf = {86source = "aztfmod/azurecaf"87version = "~> 1.2"88}89}9091# Optional: Remote state for team collaboration92backend "azurerm" {93resource_group_name = "rg-terraform-state"94storage_account_name = "tfstate${var.state_suffix}"95container_name = "tfstate"96key = "app.terraform.tfstate"97}98}99100provider "azurerm" {101features {}102}103```104105> **⚠️ IMPORTANT**: For **Azure Functions Flex Consumption**, use azurerm provider **v4.2 or later**:106> ```hcl107> terraform {108> required_providers {109> azurerm = {110> source = "hashicorp/azurerm"111> version = "~> 4.2"112> }113> }114> }115> ```116> See [Terraform Functions patterns](../../services/functions/terraform.md) for Flex Consumption examples.117118### 4. Variables and Outputs119120> ⚠️ **WARNING: Use `${VAR}` syntax in `main.tfvars.json`, NOT Go-style `{{ .Env.* }}`**121>122> azd's template engine processes `azure.yaml` and service manifests — it does **NOT** interpolate123> Go-style `{{ .Env.* }}` template variables in `.tfvars.json` or any Terraform variable files.124> Literal strings like `{{ .Env.AZURE_ENV_NAME }}` will be passed directly to Terraform, causing125> deployment failures.126>127> azd reads `infra/main.tfvars.json`, substitutes `${VAR}` references using its built-in envsubst,128> and passes the resolved file to Terraform via `-var-file=`. Use this pattern:129>130> ```json131> {132> "environment_name": "${AZURE_ENV_NAME}",133> "location": "${AZURE_LOCATION}",134> "subscription_id": "${AZURE_SUBSCRIPTION_ID}"135> }136> ```137>138> For additional variables not in `main.tfvars.json`, use **`TF_VAR_*` environment variables**:139> `azd env set TF_VAR_myvar value`140141**variables.tf:**142```hcl143variable "environment_name" {144type = string145description = "Environment name from azd"146}147148variable "location" {149type = string150description = "Azure region"151default = "eastus2"152}153154variable "principal_id" {155type = string156description = "User principal ID from azd auth"157default = ""158}159```160161**outputs.tf:**162```hcl163# Required: Resource group name164output "AZURE_RESOURCE_GROUP" {165value = azurerm_resource_group.main.name166}167168# Service-specific outputs169output "API_URL" {170value = azurerm_container_app.api.latest_revision_fqdn171}172173output "WEB_URL" {174value = azurerm_static_web_app.web.default_host_name175}176```177178> 💡 **Tip:** Output names in UPPERCASE are automatically set as azd environment variables.179180### 5. Required Tags for azd181182**CRITICAL:** Tag hosting resources with service names from azure.yaml:183184```hcl185resource "azurerm_container_app" "api" {186name = "ca-${var.environment_name}-api"187resource_group_name = azurerm_resource_group.main.name188189# Required for azd deploy to find this resource190tags = merge(var.tags, {191"azd-service-name" = "api" # Matches service name in azure.yaml192})193194# ... rest of configuration195}196197resource "azurerm_static_web_app" "web" {198name = "swa-${var.environment_name}-web"199resource_group_name = azurerm_resource_group.main.name200201# Required for azd deploy to find this resource202tags = merge(var.tags, {203"azd-service-name" = "web" # Matches service name in azure.yaml204})205206# ... rest of configuration207}208```209210> ⚠️ **WARNING:** Without `azd-service-name` tags, `azd deploy` will fail to find deployment targets.211212### 6. Resource Group Tags213214Tag the resource group with environment name:215216```hcl217resource "azurerm_resource_group" "main" {218name = "rg-${var.environment_name}"219location = var.location220221tags = {222"azd-env-name" = var.environment_name223}224}225```226227## Deployment Workflow228229### Initial Setup230231```bash232# 1. Create azd environment233azd env new dev --no-prompt234235# 2. Set required variables236azd env set AZURE_LOCATION eastus2237238# 3. Provision infrastructure (runs terraform init, plan, apply)239azd provision240241# 4. Deploy services242azd deploy243244# Or do both with single command245azd up246```247248### Variables and State249250**azd environment variables** → **Terraform variables**251252azd passes variables to Terraform through `main.tfvars.json` (with `${VAR}` substitution) or253explicit `TF_VAR_*` environment variables. Define the variable in `variables.tf` and reference254it in `main.tfvars.json`.255256infra/main.tfvars.json — azd substitutes ${VAR} references via envsubst:257```json258{259"environment_name": "${AZURE_ENV_NAME}",260"location": "${AZURE_LOCATION}",261"database_name": "${DATABASE_NAME}"262}263```264265variables.tf — value provided via main.tfvars.json or TF_VAR_database_name:266```hcl267variable "database_name" {268type = string269}270```271272For variables not in `main.tfvars.json`, use `TF_VAR_*` environment variables:273274```bash275azd env set TF_VAR_custom_setting "my-value"276```277278> ⚠️ **Use `${VAR}` syntax in `main.tfvars.json`, NOT Go-style `{{ .Env.* }}`.** azd substitutes279> `${VAR}` references using its built-in envsubst. Go-style template variables are only processed280> in `azure.yaml` and service manifests. See [Variables and Outputs](#4-variables-and-outputs) for details.281282**Remote state setup:**283284```bash285# Create state storage (one-time setup)286az group create --name rg-terraform-state --location eastus2287288az storage account create \289--name tfstate<unique> \290--resource-group rg-terraform-state \291--sku Standard_LRS292293az storage container create \294--name tfstate \295--account-name tfstate<unique>296297# Set backend variables for azd298azd env set TF_STATE_RESOURCE_GROUP rg-terraform-state299azd env set TF_STATE_STORAGE_ACCOUNT tfstate<unique>300```301302## Generation Steps303304When preparing a new azd+Terraform project:3053061. **Generate azure.yaml** with `infra.provider: terraform`3072. **Create Terraform files** in `./infra/`:308- `main.tf` - Core resources and resource group309- `variables.tf` - environment_name, location, tags310- `outputs.tf` - Service URLs and resource names (UPPERCASE)311- `provider.tf` - azurerm provider + backend config3123. **Add required tags**:313- Resource group: `azd-env-name`314- Hosting resources: `azd-service-name` (matches azure.yaml services)3154. **Research best practices** - Call `mcp_azure_mcp_azureterraformbestpractices`316317## AVM Terraform Module Priority318319For Terraform module selection, enforce this order:3203211. AVM Terraform Pattern Modules3222. AVM Terraform Resource Modules3233. AVM Terraform Utility Modules324325Use `mcp_azure_mcp_documentation` (`azure-documentation`) for current guidance and AVM context first, then use Context7 only as supplemental examples if required. If Context7 is not available, instruct the user to install it:326327```bash328npx @upstash/context7-mcp@latest329```330331## Migration from Pure Terraform332333Converting existing Terraform project to use azd:3343351. Create `azure.yaml` with services and `infra.provider: terraform`3362. Move `.tf` files to `./infra/` directory3373. Add `azd-service-name` tags to hosting resources3384. Ensure outputs include service URLs in UPPERCASE3395. Test with `azd provision` and `azd deploy`340341## CI/CD Integration342343azd can auto-generate pipelines for Terraform:344345```bash346# Generate GitHub Actions workflow347azd pipeline config348349# Generate Azure DevOps pipeline350azd pipeline config --provider azdo351```352353Generated pipelines will:354- Install Terraform355- Run `terraform init`, `plan`, `apply`356- Use azd authentication357- Deploy services with `azd deploy`358359## Comparison: azd+Terraform vs Pure Terraform360361| Aspect | Pure Terraform | azd + Terraform |362|--------|---------------|-----------------|363| **IaC** | Terraform | Terraform |364| **Provision** | `terraform apply` | `azd provision` (wraps terraform) |365| **Deploy apps** | Manual scripts | `azd deploy` (automatic) |366| **Environment mgmt** | Workspaces | `azd env` |367| **Auth** | Manual az login | `azd auth login` |368| **CI/CD** | Manual setup | `azd pipeline config` |369| **Multi-service** | Manual orchestration | Automatic from azure.yaml |370| **Learning curve** | Medium | Low |371372## When NOT to Use azd+Terraform373374Use pure Terraform (without azd) when:375- Multi-cloud deployment (not Azure-first)376- Complex Terraform modules/workspaces that conflict with azd conventions377- Existing complex Terraform CI/CD that's hard to migrate378- Team has strong Terraform expertise but no bandwidth for azd learning379380## Azure Policy Compliance381382Enterprise Azure subscriptions typically enforce security policies. Your Terraform must comply:383384### Storage Account (Required for Functions)385386```hcl387resource "azurerm_storage_account" "storage" {388name = "stmyapp${random_string.suffix.result}"389resource_group_name = azurerm_resource_group.rg.name390location = azurerm_resource_group.rg.location391account_tier = "Standard"392account_replication_type = "LRS"393394# Azure policy requirements395allow_nested_items_to_be_public = false # Disable anonymous blob access396local_user_enabled = false # Disable local users397shared_access_key_enabled = false # RBAC-only, no access keys398}399```400401### Function App with Managed Identity Storage402403```hcl404provider "azurerm" {405features {}406storage_use_azuread = true # Required when shared_access_key_enabled = false407}408409resource "azurerm_linux_function_app" "function" {410name = "func-myapp"411resource_group_name = azurerm_resource_group.rg.name412location = azurerm_resource_group.rg.location413service_plan_id = azurerm_service_plan.plan.id414storage_account_name = azurerm_storage_account.storage.name415storage_uses_managed_identity = true # Use MI instead of access key416417identity {418type = "SystemAssigned"419}420421tags = {422"azd-service-name" = "api" # REQUIRED for azd deploy423}424425depends_on = [azurerm_role_assignment.deployer_storage]426}427428# RBAC for deploying user (create function with MI storage)429resource "azurerm_role_assignment" "deployer_storage" {430scope = azurerm_storage_account.storage.id431role_definition_name = "Storage Blob Data Owner"432principal_id = data.azurerm_client_config.current.object_id433}434435# RBAC for function app after creation436resource "azurerm_role_assignment" "function_storage" {437scope = azurerm_storage_account.storage.id438role_definition_name = "Storage Blob Data Owner"439principal_id = azurerm_linux_function_app.function.identity[0].principal_id440}441```442443### Services with Disabled Local Auth444445```hcl446# Service Bus447resource "azurerm_servicebus_namespace" "sb" {448local_auth_enabled = false # RBAC-only449}450451# Event Hubs452resource "azurerm_eventhub_namespace" "eh" {453local_authentication_enabled = false # RBAC-only454}455456# Cosmos DB457resource "azurerm_cosmosdb_account" "cosmos" {458local_authentication_disabled = true # RBAC-only459}460```461462## Troubleshooting463464| Issue | Solution |465|-------|----------|466| `resource not found: unable to find a resource tagged with 'azd-service-name'` | Add `azd-service-name` tag to hosting resource in Terraform |467| `RequestDisallowedByPolicy: shared key access` | Set `shared_access_key_enabled = false` on storage |468| `RequestDisallowedByPolicy: local auth disabled` | Set `local_auth_enabled = false` on Service Bus |469| `RequestDisallowedByPolicy: anonymous blob access` | Set `allow_nested_items_to_be_public = false` on storage |470| `terraform command not found` | Install Terraform CLI: `brew install terraform` or download from terraform.io |471| State conflicts | Configure remote backend in provider.tf |472| Variable not passed to Terraform | Ensure variable is set with `azd env set` and defined in variables.tf |473| Literal `{{ .Env.* }}` in Terraform errors | Use `${VAR}` syntax in `main.tfvars.json`, not Go-style `{{ .Env.* }}`. azd substitutes `${VAR}` references via envsubst |474| `main.tfvars.json` interpolation failure | Ensure `main.tfvars.json` uses `${VAR}` syntax (e.g., `${AZURE_ENV_NAME}`), not Go-style `{{ .Env.* }}` templates |475476## References477478- [Microsoft Docs: Use Terraform with azd](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/use-terraform-for-azd)479- [azd-starter-terraform template](https://github.com/Azure-Samples/azd-starter-terraform)480- [Terraform Azure Provider](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs)481- [Azure CAF Naming](https://registry.terraform.io/providers/aztfmod/azurecaf/latest/docs)482