Imported from sickn33/agentic-awesome-skills (
skills/terraform-azure/SKILL.md). Install upstream withnpx skills add sickn33/agentic-awesome-skills --skill terraform-azure. Copyright stays with the author (MIT).
Terraform Azure
Provision and manage Azure infrastructure with Terraform using the AzureRM provider. Covers provider configuration, remote state, resource groups, VNets, AKS, Key Vault, complete .tf file examples, and production workflows.
Prerequisites
# Prerequisite: reviewed, organization-approved Terraform package installed.
# Verify installation
terraform version
# Install Azure CLI and login
# Prerequisite: organization-approved Azure CLI installed from a verified signed package.
az version
az login
az account set --subscription "my-subscription-id"
# Create storage account for remote state
az group create --name tfstate-rg --location eastus
TFSTATE_ACCOUNT="tfstate$(openssl rand -hex 4)"
az storage account create \
--name "$TFSTATE_ACCOUNT" \
--resource-group tfstate-rg \
--sku Standard_LRS \
--encryption-services blob
az storage container create \
--name tfstate \
--account-name "$TFSTATE_ACCOUNT" --auth-mode login
These historical AzureRM3.x/AzureAD2.x fragments must be evaluated against a pinned compatible provider/lockfile; do not silently upgrade to latest. Confirm subscription/tenant and use short-lived least-privilege credentials. Provisioning/state changes require an approved target, cost review and recoverable plan. Restrict/encrypt/version the backend and grant intended Blob data roles; sensitive only redacts display, not secrets in state/plans. Save artifacts privately and never commit credentials/state.
Provider Configuration
providers.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.80"
}
azuread = {
source = "hashicorp/azuread"
version = "~> 2.47"
}
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
}
backend "azurerm" {
resource_group_name = "tfstate-rg"
storage_account_name = "REPLACE_WITH_CREATED_ACCOUNT"
use_azuread_auth = true
container_name = "tfstate"
key = "prod.terraform.tfstate"
}
}
provider "azurerm" {
features {
key_vault {
purge_soft_delete_on_destroy = false
recover_soft_deleted_key_vaults = true
}
resource_group {
prevent_deletion_if_contains_resources = true
}
}
# Optional: use a specific subscription
# subscription_id = var.subscription_id
}
provider "azuread" {}
variables.tf
variable "environment" {
description = "Environment name (dev, staging, prod)"
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
variable "location" {
description = "Azure region for all resources"
type = string
default = "eastus"
}
variable "project_name" {
description = "Project name used in resource naming"
type = string
default = "myapp"
}
variable "tags" {
description = "Tags applied to all resources"
type = map(string)
default = {}
}
variable "sql_admin_password" {
description = "SQL Server admin password"
type = string
sensitive = true
}
variable "allowed_ip_ranges" {
description = "Approved Key Vault administrator IP ranges"
type = list(string)
default = []
}
variable "kubernetes_version" {
description = "Explicit AKS version verified supported for the target region"
type = string
}
variable "aks_admin_group_id" {
description = "Azure AD group ID for AKS admin access"
type = string
}
locals {
name_prefix = "${var.project_name}-${var.environment}"
common_tags = merge(var.tags, {
environment = var.environment
project = var.project_name
managed_by = "terraform"
})
}
terraform.tfvars (per environment)
# terraform.prod.tfvars
environment = "prod"
location = "eastus"
project_name = "myapp"
aks_admin_group_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
tags = {
cost_center = "engineering"
owner = "platform-team"
}
Resource Group
resource-group.tf
resource "azurerm_resource_group" "main" {
name = "${local.name_prefix}-rg"
location = var.location
tags = local.common_tags
}
Virtual Network
network.tf
resource "azurerm_virtual_network" "main" {
name = "${local.name_prefix}-vnet"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
address_space = ["10.0.0.0/16"]
tags = local.common_tags
}
resource "azurerm_subnet" "aks" {
name = "aks-subnet"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = ["10.0.0.0/22"]
service_endpoints = ["Microsoft.KeyVault"]
}
resource "azurerm_subnet" "app" {
name = "app-subnet"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = ["10.0.8.0/24"]
service_endpoints = ["Microsoft.KeyVault"]
}
resource "azurerm_subnet" "data" {
name = "data-subnet"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = ["10.0.9.0/24"]
private_endpoint_network_policies_enabled = true
}
resource "azurerm_network_security_group" "app" {
name = "${local.name_prefix}-app-nsg"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
security_rule {
name = "AllowHTTPS"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "443"
source_address_prefix = "*"
destination_address_prefix = "*"
}
tags = local.common_tags
}
resource "azurerm_subnet_network_security_group_association" "app" {
subnet_id = azurerm_subnet.app.id
network_security_group_id = azurerm_network_security_group.app.id
}
AKS Cluster
aks.tf
resource "azurerm_log_analytics_workspace" "aks" {
name = "${local.name_prefix}-law"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
sku = "PerGB2018"
retention_in_days = 30
tags = local.common_tags
}
resource "azurerm_kubernetes_cluster" "main" {
name = "${local.name_prefix}-aks"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
dns_prefix = "${var.project_name}-${var.environment}"
kubernetes_version = var.kubernetes_version # verified supported in region
default_node_pool {
name = "system"
vm_size = "Standard_D4s_v5"
enable_auto_scaling = true
min_count = 2
max_count = 5
zones = [1, 2, 3]
vnet_subnet_id = azurerm_subnet.aks.id
os_disk_size_gb = 128
os_disk_type = "Managed"
max_pods = 50
node_labels = {
role = "system"
}
}
identity {
type = "SystemAssigned"
}
network_profile {
network_plugin = "azure"
network_policy = "calico"
service_cidr = "10.1.0.0/16"
dns_service_ip = "10.1.0.10"
load_balancer_sku = "standard"
}
azure_active_directory_role_based_access_control {
managed = true
azure_rbac_enabled = true
admin_group_object_ids = [var.aks_admin_group_id]
}
oms_agent {
log_analytics_workspace_id = azurerm_log_analytics_workspace.aks.id
}
key_vault_secrets_provider {
secret_rotation_enabled = true
secret_rotation_interval = "2m"
}
tags = local.common_tags
}
resource "azurerm_kubernetes_cluster_node_pool" "app" {
name = "app"
kubernetes_cluster_id = azurerm_kubernetes_cluster.main.id
vm_size = "Standard_D8s_v5"
enable_auto_scaling = true
min_count = 2
max_count = 20
zones = [1, 2, 3]
vnet_subnet_id = azurerm_subnet.aks.id
max_pods = 50
node_labels = {
workload = "app"
}
node_taints = [
"dedicated=app:NoSchedule"
]
tags = local.common_tags
}
Key Vault
keyvault.tf
data "azurerm_client_config" "current" {}
resource "azurerm_key_vault" "main" {
name = "${var.project_name}-${var.environment}-kv"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
tenant_id = data.azurerm_client_config.current.tenant_id
sku_name = "standard"
soft_delete_retention_days = 90
purge_protection_enabled = true
enabled_for_disk_encryption = true
network_acls {
default_action = "Deny"
bypass = "AzureServices"
ip_rules = var.allowed_ip_ranges
virtual_network_subnet_ids = [
azurerm_subnet.app.id,
azurerm_subnet.aks.id,
]
}
tags = local.common_tags
}
resource "azurerm_key_vault_access_policy" "terraform" {
key_vault_id = azurerm_key_vault.main.id
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = data.azurerm_client_config.current.object_id
secret_permissions = [
"Get", "List", "Set", "Delete", "Recover"
]
key_permissions = [
"Get", "List", "Create", "Delete", "Recover",
"WrapKey", "UnwrapKey"
]
}
resource "azurerm_key_vault_access_policy" "aks" {
key_vault_id = azurerm_key_vault.main.id
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = azurerm_kubernetes_cluster.main.key_vault_secrets_provider[0].secret_identity[0].object_id
secret_permissions = ["Get", "List"]
}
resource "azurerm_key_vault_secret" "sql_password" {
name = "sql-admin-password"
value = var.sql_admin_password
key_vault_id = azurerm_key_vault.main.id
depends_on = [azurerm_key_vault_access_policy.terraform]
}
Contents
When to Use
- You need multi-cloud or cloud-agnostic Infrastructure as Code.
- Your team standardizes on Terraform across AWS, Azure, and GCP.
- You need plan/apply workflows with change preview before deployment.
- You want modular, reusable infrastructure components.
- You need state locking and drift detection for production infrastructure.
Limitations
- Infrastructure commands can disrupt services: confirm target host/scope and have backups/snapshots before mutating state.
- Docs-only import: upstream scripts and templates not bundled.
