All skills
microsoft avatar

/azure-prepare

@b8a1c66
by microsoftmicrosoft/skills3.1k stars
351

Prepare azd-based Azure projects for deployment: generates azure.yaml, infrastructure (Bicep/Terraform), and Dockerfiles for the Azure Developer CLI (azd) workflow. USE ONLY when the user explicitly wants to use azd as the deployment tool, or the project already has an azure.yaml file. DO NOT USE FOR: non-azd deployments, Python App Service code-only deploys (use python-appservice-deploy), or cross-cloud migration (use azure-cloud-migrate). WHEN: prepare app for azd, create azure.yaml, set up azd infrastructure, modernize app for Azure with azd, deploy with azd, function app, timer trigger, service bus trigger, event-driven function, managed identity, generate Bicep, generate Terraform, create and deploy to Azure.

Use this Skill: https://skilld.dev/gh/microsoft/skills/azure-prepare

This session only. Nothing lands on disk.

referencesrecipesazdterraform.md

≈3.6k tokens on demand. Your agent reads this file only when SKILL.md points to it.

AZD with Terraform

Use Azure Developer CLI (azd) with Terraform as the infrastructure provider.

When to Use

Choose azd+Terraform when you want:

  • Terraform's multi-cloud capabilities with azd's deployment simplicity
  • Existing Terraform expertise but want azd up convenience
  • Team familiar with Terraform but needs environment management
  • Multi-cloud IaC with Azure-first deployment experience

Benefits

Feature Pure Terraform AZD + Terraform
Deploy command terraform apply azd up
Environment management Manual workspaces Built-in azd env
CI/CD generation Manual setup Auto-generated pipelines
Service deployment Manual scripts Automatic from azure.yaml
State management Manual backend setup Configurable
Multi-cloud ✅ Yes ✅ Yes

Configuration

1. azure.yaml Structure

Create azure.yaml in project root:

name: myapp
metadata:
  template: azd-init

# Specify Terraform as IaC provider
infra:
  provider: terraform
  path: ./infra

# Define services as usual
services:
  api:
    project: ./src/api
    language: python
    host: containerapp
    docker:
      path: ./src/api/Dockerfile
  
  web:
    project: ./src/web
    language: js
    host: staticwebapp
    dist: dist

2. Terraform File Structure

Place Terraform files in ./infra/:

infra/
├── main.tf              # Main resources
├── variables.tf         # Variable definitions
├── outputs.tf           # Output values
├── provider.tf          # Provider configuration
└── modules/
    ├── api/
    │   └── main.tf
    └── web/
        └── main.tf

3. Provider Configuration

provider.tf:

terraform {
  required_version = ">= 1.5.0"

  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 4.2"
    }
    azurecaf = {
      source  = "aztfmod/azurecaf"
      version = "~> 1.2"
    }
  }

  # Optional: Remote state for team collaboration
  backend "azurerm" {
    resource_group_name  = "rg-terraform-state"
    storage_account_name = "tfstate${var.state_suffix}"
    container_name       = "tfstate"
    key                  = "app.terraform.tfstate"
  }
}

provider "azurerm" {
  features {}
}

⚠️ IMPORTANT: For Azure Functions Flex Consumption, use azurerm provider v4.2 or later:

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 4.2"
    }
  }
}

See Terraform Functions patterns for Flex Consumption examples.

4. Variables and Outputs

⚠️ WARNING: Use ${VAR} syntax in main.tfvars.json, NOT Go-style {{ .Env.* }}

azd's template engine processes azure.yaml and service manifests — it does NOT interpolate Go-style {{ .Env.* }} template variables in .tfvars.json or any Terraform variable files. Literal strings like {{ .Env.AZURE_ENV_NAME }} will be passed directly to Terraform, causing deployment failures.

azd reads infra/main.tfvars.json, substitutes ${VAR} references using its built-in envsubst, and passes the resolved file to Terraform via -var-file=. Use this pattern:

{
    "environment_name": "${AZURE_ENV_NAME}",
    "location": "${AZURE_LOCATION}",
    "subscription_id": "${AZURE_SUBSCRIPTION_ID}"
}

For additional variables not in main.tfvars.json, use TF_VAR_* environment variables: azd env set TF_VAR_myvar value

variables.tf:

variable "environment_name" {
  type        = string
  description = "Environment name from azd"
}

variable "location" {
  type        = string
  description = "Azure region"
  default     = "eastus2"
}

variable "principal_id" {
  type        = string
  description = "User principal ID from azd auth"
  default     = ""
}

outputs.tf:

# Required: Resource group name
output "AZURE_RESOURCE_GROUP" {
  value = azurerm_resource_group.main.name
}

# Service-specific outputs
output "API_URL" {
  value = azurerm_container_app.api.latest_revision_fqdn
}

output "WEB_URL" {
  value = azurerm_static_web_app.web.default_host_name
}

💡 Tip: Output names in UPPERCASE are automatically set as azd environment variables.

5. Required Tags for azd

CRITICAL: Tag hosting resources with service names from azure.yaml:

resource "azurerm_container_app" "api" {
  name                = "ca-${var.environment_name}-api"
  resource_group_name = azurerm_resource_group.main.name
  
  # Required for azd deploy to find this resource
  tags = merge(var.tags, {
    "azd-service-name" = "api"  # Matches service name in azure.yaml
  })
  
  # ... rest of configuration
}

resource "azurerm_static_web_app" "web" {
  name                = "swa-${var.environment_name}-web"
  resource_group_name = azurerm_resource_group.main.name
  
  # Required for azd deploy to find this resource
  tags = merge(var.tags, {
    "azd-service-name" = "web"  # Matches service name in azure.yaml
  })
  
  # ... rest of configuration
}

⚠️ WARNING: Without azd-service-name tags, azd deploy will fail to find deployment targets.

6. Resource Group Tags

Tag the resource group with environment name:

resource "azurerm_resource_group" "main" {
  name     = "rg-${var.environment_name}"
  location = var.location
  
  tags = {
    "azd-env-name" = var.environment_name
  }
}

Deployment Workflow

Initial Setup

# 1. Create azd environment
azd env new dev --no-prompt

# 2. Set required variables
azd env set AZURE_LOCATION eastus2

# 3. Provision infrastructure (runs terraform init, plan, apply)
azd provision

# 4. Deploy services
azd deploy

# Or do both with single command
azd up

Variables and State

azd environment variables → Terraform variables

azd passes variables to Terraform through main.tfvars.json (with ${VAR} substitution) or explicit TF_VAR_* environment variables. Define the variable in variables.tf and reference it in main.tfvars.json.

infra/main.tfvars.json — azd substitutes ${VAR} references via envsubst:

{
    "environment_name": "${AZURE_ENV_NAME}",
    "location": "${AZURE_LOCATION}",
    "database_name": "${DATABASE_NAME}"
}

variables.tf — value provided via main.tfvars.json or TF_VAR_database_name:

variable "database_name" {
  type = string
}

For variables not in main.tfvars.json, use TF_VAR_* environment variables:

azd env set TF_VAR_custom_setting "my-value"

⚠️ Use ${VAR} syntax in main.tfvars.json, NOT Go-style {{ .Env.* }}. azd substitutes ${VAR} references using its built-in envsubst. Go-style template variables are only processed in azure.yaml and service manifests. See Variables and Outputs for details.

Remote state setup:

# Create state storage (one-time setup)
az group create --name rg-terraform-state --location eastus2

az storage account create \
  --name tfstate<unique> \
  --resource-group rg-terraform-state \
  --sku Standard_LRS

az storage container create \
  --name tfstate \
  --account-name tfstate<unique>

# Set backend variables for azd
azd env set TF_STATE_RESOURCE_GROUP rg-terraform-state
azd env set TF_STATE_STORAGE_ACCOUNT tfstate<unique>

Generation Steps

When preparing a new azd+Terraform project:

  1. Generate azure.yaml with infra.provider: terraform
  2. Create Terraform files in ./infra/:
    • main.tf - Core resources and resource group
    • variables.tf - environment_name, location, tags
    • outputs.tf - Service URLs and resource names (UPPERCASE)
    • provider.tf - azurerm provider + backend config
  3. Add required tags:
    • Resource group: azd-env-name
    • Hosting resources: azd-service-name (matches azure.yaml services)
  4. Research best practices - Call mcp_azure_mcp_azureterraformbestpractices

AVM Terraform Module Priority

For Terraform module selection, enforce this order:

  1. AVM Terraform Pattern Modules
  2. AVM Terraform Resource Modules
  3. AVM Terraform Utility Modules

Use 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:

npx @upstash/context7-mcp@latest

Migration from Pure Terraform

Converting existing Terraform project to use azd:

  1. Create azure.yaml with services and infra.provider: terraform
  2. Move .tf files to ./infra/ directory
  3. Add azd-service-name tags to hosting resources
  4. Ensure outputs include service URLs in UPPERCASE
  5. Test with azd provision and azd deploy

CI/CD Integration

azd can auto-generate pipelines for Terraform:

# Generate GitHub Actions workflow
azd pipeline config

# Generate Azure DevOps pipeline
azd pipeline config --provider azdo

Generated pipelines will:

  • Install Terraform
  • Run terraform init, plan, apply
  • Use azd authentication
  • Deploy services with azd deploy

Comparison: azd+Terraform vs Pure Terraform

Aspect Pure Terraform azd + Terraform
IaC Terraform Terraform
Provision terraform apply azd provision (wraps terraform)
Deploy apps Manual scripts azd deploy (automatic)
Environment mgmt Workspaces azd env
Auth Manual az login azd auth login
CI/CD Manual setup azd pipeline config
Multi-service Manual orchestration Automatic from azure.yaml
Learning curve Medium Low

When NOT to Use azd+Terraform

Use pure Terraform (without azd) when:

  • Multi-cloud deployment (not Azure-first)
  • Complex Terraform modules/workspaces that conflict with azd conventions
  • Existing complex Terraform CI/CD that's hard to migrate
  • Team has strong Terraform expertise but no bandwidth for azd learning

Azure Policy Compliance

Enterprise Azure subscriptions typically enforce security policies. Your Terraform must comply:

Storage Account (Required for Functions)

resource "azurerm_storage_account" "storage" {
  name                            = "stmyapp${random_string.suffix.result}"
  resource_group_name             = azurerm_resource_group.rg.name
  location                        = azurerm_resource_group.rg.location
  account_tier                    = "Standard"
  account_replication_type        = "LRS"
  
  # Azure policy requirements
  allow_nested_items_to_be_public = false   # Disable anonymous blob access
  local_user_enabled              = false   # Disable local users
  shared_access_key_enabled       = false   # RBAC-only, no access keys
}

Function App with Managed Identity Storage

provider "azurerm" {
  features {}
  storage_use_azuread = true   # Required when shared_access_key_enabled = false
}

resource "azurerm_linux_function_app" "function" {
  name                          = "func-myapp"
  resource_group_name           = azurerm_resource_group.rg.name
  location                      = azurerm_resource_group.rg.location
  service_plan_id               = azurerm_service_plan.plan.id
  storage_account_name          = azurerm_storage_account.storage.name
  storage_uses_managed_identity = true   # Use MI instead of access key
  
  identity {
    type = "SystemAssigned"
  }
  
  tags = {
    "azd-service-name" = "api"   # REQUIRED for azd deploy
  }
  
  depends_on = [azurerm_role_assignment.deployer_storage]
}

# RBAC for deploying user (create function with MI storage)
resource "azurerm_role_assignment" "deployer_storage" {
  scope                = azurerm_storage_account.storage.id
  role_definition_name = "Storage Blob Data Owner"
  principal_id         = data.azurerm_client_config.current.object_id
}

# RBAC for function app after creation
resource "azurerm_role_assignment" "function_storage" {
  scope                = azurerm_storage_account.storage.id
  role_definition_name = "Storage Blob Data Owner"
  principal_id         = azurerm_linux_function_app.function.identity[0].principal_id
}

Services with Disabled Local Auth

# Service Bus
resource "azurerm_servicebus_namespace" "sb" {
  local_auth_enabled = false   # RBAC-only
}

# Event Hubs
resource "azurerm_eventhub_namespace" "eh" {
  local_authentication_enabled = false   # RBAC-only
}

# Cosmos DB
resource "azurerm_cosmosdb_account" "cosmos" {
  local_authentication_disabled = true   # RBAC-only
}

Troubleshooting

Issue Solution
resource not found: unable to find a resource tagged with 'azd-service-name' Add azd-service-name tag to hosting resource in Terraform
RequestDisallowedByPolicy: shared key access Set shared_access_key_enabled = false on storage
RequestDisallowedByPolicy: local auth disabled Set local_auth_enabled = false on Service Bus
RequestDisallowedByPolicy: anonymous blob access Set allow_nested_items_to_be_public = false on storage
terraform command not found Install Terraform CLI: brew install terraform or download from terraform.io
State conflicts Configure remote backend in provider.tf
Variable not passed to Terraform Ensure variable is set with azd env set and defined in variables.tf
Literal {{ .Env.* }} in Terraform errors Use ${VAR} syntax in main.tfvars.json, not Go-style {{ .Env.* }}. azd substitutes ${VAR} references via envsubst
main.tfvars.json interpolation failure Ensure main.tfvars.json uses ${VAR} syntax (e.g., ${AZURE_ENV_NAME}), not Go-style {{ .Env.* }} templates

References

Source: SKILL.md on GitHub

2 warnings3d4 checks · Risk SAFE
  • Gen Agent Trust Hub3d

    This skill includes security considerations related to the processing of untrusted project files and the retrieval of external development templates. While these operations are essential for modernizing and preparing Azure applications, they represent a surface area for indirect prompt injection and depend on the integrity of external template repositories.

  • Socket3d

    5 alerts: gptAnomaly, gptSecurity

  • Snyk3d

    Risk: LOW · No issues

  • Runlayer7mo

    86/87 files flagged

Signed by skilld at b8a1c66. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 20 hours ago.

Activeupdated last week
metadata
{
  "author": "Microsoft",
  "version": "1.3.4"
}

README badge

README badge for microsoft/skills/azure-prepare