Deploy infrastructure to Azure using Terraform following industry best practices. Use this skill whenever the user mentions Azure deployment, Terraform with Azure, Azure infrastructure as code, cloud provisioning on Azure, or wants to create/manage any Azure resources programmatically. Also trigger when the user mentions specific Azure services like App Service, AKS, Azure SQL, Storage Accounts, VNets, or Container Apps in the context of infrastructure setup. Even if the user just says "deploy to Azure" or "set up Azure infra", use this skill.
Deploy Azure infrastructure with Terraform using production-grade patterns without overengineering.
Before writing any Terraform, determine the deployment type:
What are you deploying?
├─ Static site / SPA → Read references/app-service.md (Static Web App section)
├─ Web API / Web App → Read references/app-service.md
├─ Containers (single service) → Read references/container-apps.md
├─ Containers (orchestration) → Read references/aks.md
├─ Database only → Read references/data.md
├─ Full-stack app → Combine relevant references
└─ Networking / VNet setup → Read references/networking.md
Use this standard layout. Do NOT create nested module hierarchies for small/medium projects.
infra/
├── main.tf # Provider config + resource group
├── variables.tf # Input variables with descriptions and validation
├── outputs.tf # Useful outputs (URLs, connection strings, IDs)
├── terraform.tfvars # Default values (NOT secrets — gitignored if contains sensitive data)
├── <resource>.tf # One file per logical resource group (e.g., app.tf, db.tf, network.tf)
└── environments/ # Only if 2+ environments
├── dev.tfvars
├── staging.tfvars
└── prod.tfvars
For projects with 3+ environments or team collaboration, add:
├── backend.tf # Remote state config (Azure Storage)
When to use modules: Only when you have genuinely reusable infrastructure patterns across 2+ projects. A single project does NOT need custom modules — use resource blocks directly.
# main.tf
terraform {
required_version = ">= 1.5"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0"
}
}
}
provider "azurerm" {
features {}
subscription_id = var.subscription_id
}
resource "azurerm_resource_group" "main" {
name = "rg-${var.project_name}-${var.environment}"
location = var.location
tags = local.common_tags
}
locals {
common_tags = {
project = var.project_name
environment = var.environment
managed_by = "terraform"
}
}
# variables.tf — Always include these base variables
variable "project_name" {
description = "Short project identifier used in resource naming"
type = string
validation {
condition = can(regex("^[a-z0-9-]+$", var.project_name))
error_message = "Project name must be lowercase alphanumeric with hyphens only."
}
}
variable "environment" {
description = "Deployment environment"
type = string
default = "dev"
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
variable "location" {
description = "Azure region"
type = string
default = "eastus2"
}
variable "subscription_id" {
description = "Azure subscription ID"
type = string
sensitive = true
}
# outputs.tf
output "resource_group_name" {
value = azurerm_resource_group.main.name
}
Follow Microsoft's Cloud Adoption Framework abbreviations:
| Resource | Prefix | Example |
|---|---|---|
| Resource Group | rg- | rg-myapp-dev |
| App Service | app- | app-myapp-dev |
| App Service Plan | asp- | asp-myapp-dev |
| Container App | ca- | ca-myapp-api-dev |
| Container App Env | cae- | cae-myapp-dev |
| SQL Server | sql- | sql-myapp-dev |
| SQL Database | sqldb- | sqldb-myapp-dev |
| Storage Account | st | stmyappdev (no hyphens, max 24 chars) |
| Key Vault | kv- | kv-myapp-dev |
| Virtual Network | vnet- | vnet-myapp-dev |
| AKS Cluster | aks- | aks-myapp-dev |
Pattern: {prefix}{project}-{environment}
Apply these to EVERY deployment:
sensitive = true on variables, reference Key VaultSystemAssigned identity over connection stringshttps_only = true on all web resourcesminimum_tls_version = "1.2" everywhere availablelocal.common_tags on all resourcesAdd when: team > 1 person OR deploying to staging/prod.
# backend.tf
terraform {
backend "azurerm" {
resource_group_name = "rg-terraform-state"
storage_account_name = "stterraformstate"
container_name = "tfstate"
key = "myapp.terraform.tfstate"
}
}
Bootstrap script for state storage — read references/remote-state.md.
cd infra/
az login
terraform init
terraform plan -var-file="environments/dev.tfvars"
terraform apply -var-file="environments/dev.tfvars"
.tf fileterraform plan to previewterraform apply after reviewoutputs.tf if the user needs valuesterraform destroy -var-file="environments/dev.tfvars"
count when for_each with a map is clearerterraform plan before apply.tfstate in gitlatest tags for container imagesRead these based on what you're deploying:
| File | When to Read |
|---|---|
references/app-service.md | Web apps, APIs, static sites on App Service |
references/container-apps.md | Containerized workloads without Kubernetes |
references/aks.md | Kubernetes clusters |
references/data.md | SQL, PostgreSQL, Cosmos DB, Storage Accounts |
references/networking.md | VNets, subnets, NSGs, private endpoints |
references/remote-state.md | Setting up remote state backend |
references/cicd.md | GitHub Actions / Azure DevOps pipelines for Terraform |