Azure Recipes
Production-ready examples combining multiple terraform-az-modules modules. Each recipe is self-contained — copy, adjust the locals, and run.
Recipe 1: AKS + ACR + Key Vault
The most common production stack: a private AKS cluster with a dedicated container registry and secrets managed in Key Vault.
What gets created:
- Resource Group
- Virtual Network
- Key Vault (RBAC-enabled, public access disabled)
- Azure Container Registry (Premium SKU, encrypted via Key Vault)
- AKS cluster (private, Azure CNI, connected to ACR)
# versions.tf
terraform {
required_version = ">= 1.6.6"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = ">= 3.90.0"
}
}
}
provider "azurerm" {
features {}
}
# main.tf
locals {
name = "myapp"
environment = "prod"
location = "eastus"
}
# ── Resource Group ────────────────────────────────────────────
module "resource_group" {
source = "git::https://github.com/terraform-az-modules/terraform-azurerm-resource-group.git?ref=v1.0.0"
name = local.name
environment = local.environment
location = local.location
}
# ── Virtual Network ───────────────────────────────────────────
module "vnet" {
source = "git::https://github.com/terraform-az-modules/terraform-azurerm-vnet.git?ref=v1.0.0"
name = local.name
environment = local.environment
location = local.location
resource_group_name = module.resource_group.resource_group_name
address_spaces = ["10.0.0.0/16"]
}
# ── Key Vault ─────────────────────────────────────────────────
module "key_vault" {
source = "git::https://github.com/terraform-az-modules/terraform-azurerm-key-vault.git?ref=v1.0.0"
name = local.name
environment = local.environment
location = local.location
resource_group_name = module.resource_group.resource_group_name
sku_name = "standard"
purge_protection_enabled = true
soft_delete_retention_days = 90
enable_rbac_authorization = true
public_network_access_enabled = false
}
# ── Container Registry ────────────────────────────────────────
module "acr" {
source = "git::https://github.com/terraform-az-modules/terraform-azurerm-acr.git?ref=v1.0.0"
name = local.name
environment = local.environment
location = local.location
resource_group_name = module.resource_group.resource_group_name
container_registry_config = {
sku = "Premium"
quarantine_policy_enabled = true
zone_redundancy_enabled = true
}
encryption = true
key_vault_id = module.key_vault.id
}
# ── AKS Cluster ───────────────────────────────────────────────
module "aks" {
source = "git::https://github.com/terraform-az-modules/terraform-azurerm-aks.git?ref=v1.0.0"
name = local.name
environment = local.environment
location = local.location
resource_group_name = module.resource_group.resource_group_name
kubernetes_version = "1.29"
agents_size = "Standard_D2s_v3"
os_disk_size_gb = 50
private_cluster_enabled = true
network_plugin = "azure"
network_policy = "azure"
service_cidr = "10.1.0.0/16"
nodes_subnet_id = module.vnet.vnet_id # replace with a dedicated subnet output
# Grant AKS permission to pull from ACR
acr_enabled = true
acr_id = module.acr.container_registry_id
# Mount Key Vault secrets as Kubernetes secrets
key_vault_secrets_provider_enabled = true
key_vault_id = module.key_vault.id
}
# outputs.tf
output "aks_name" {
value = module.aks.name
}
output "acr_login_server" {
value = module.acr.container_registry_login_server
}
output "key_vault_uri" {
value = module.key_vault.vault_uri
}
output "kube_config" {
value = module.aks.kube_config_raw
sensitive = true
}
Connect kubectl after apply:
terraform apply
terraform output -raw kube_config > ~/.kube/config
kubectl get nodes
Recipe 2: VNet + NSG (network baseline)
A reusable network stack — VNet with an NSG allowing only HTTPS inbound.
# main.tf
locals {
name = "core"
environment = "prod"
location = "westeurope"
}
module "resource_group" {
source = "git::https://github.com/terraform-az-modules/terraform-azurerm-resource-group.git?ref=v1.0.0"
name = local.name
environment = local.environment
location = local.location
}
module "vnet" {
source = "git::https://github.com/terraform-az-modules/terraform-azurerm-vnet.git?ref=v1.0.0"
name = local.name
environment = local.environment
location = local.location
resource_group_name = module.resource_group.resource_group_name
address_spaces = ["10.10.0.0/16"]
}
module "nsg" {
source = "git::https://github.com/terraform-az-modules/terraform-azurerm-nsg.git?ref=v1.0.0"
name = local.name
environment = local.environment
location = local.location
resource_group_name = module.resource_group.resource_group_name
subnet_association = true
subnet_ids = [] # add your subnet IDs
inbound_rules = [
{
name = "allow-https"
priority = 100
access = "Allow"
protocol = "Tcp"
source_address_prefix = "*"
source_port_range = "*"
destination_address_prefix = "*"
destination_port_range = "443"
},
{
name = "allow-http"
priority = 110
access = "Allow"
protocol = "Tcp"
source_address_prefix = "*"
source_port_range = "*"
destination_address_prefix = "*"
destination_port_range = "80"
},
{
name = "deny-all"
priority = 4096
access = "Deny"
protocol = "*"
source_address_prefix = "*"
source_port_range = "*"
destination_address_prefix = "*"
destination_port_range = "*"
},
]
}
# outputs.tf
output "vnet_id" {
value = module.vnet.vnet_id
}
output "vnet_address_space" {
value = module.vnet.vnet_address_space
}
Variable reference for key modules
resource-group
| Variable | Default | Description |
|---|---|---|
name | — | Short name, e.g. app |
environment | "" | prod, dev, staging |
location | null | Azure region |
enabled | true | Toggle resource creation |
resource_lock_enabled | false | Enable CanNotDelete lock |
lock_level | "CanNotDelete" | Lock type |
Outputs: resource_group_id, resource_group_name, resource_group_location
vnet
| Variable | Default | Description |
|---|---|---|
resource_group_name | — | Target resource group |
address_spaces | [] | CIDR blocks, e.g. ["10.0.0.0/16"] |
dns_servers | [] | Custom DNS (empty = Azure DNS) |
enable_ddos_pp | false | Create a DDoS Protection Plan |
enable_network_watcher | false | Deploy Network Watcher |
Outputs: vnet_id, vnet_name, vnet_address_space
aks
| Variable | Default | Description |
|---|---|---|
kubernetes_version | "1.27.7" | Kubernetes version to deploy |
agents_size | "Standard_D2s_v3" | Default node pool VM size |
agents_pool_name | "nodepool" | Default node pool name |
os_disk_size_gb | 50 | Node OS disk size in GB |
private_cluster_enabled | true | Make the API server private |
network_plugin | "azure" | azure or kubenet |
network_policy | "azure" | azure or calico |
acr_enabled | false | Grant AKS pull access to an ACR |
acr_id | null | ACR resource ID (when acr_enabled = true) |
key_vault_secrets_provider_enabled | false | Enable Key Vault CSI driver |
enable_auto_scaling | false | Enable cluster autoscaler |
agents_min_count | null | Minimum nodes (autoscaler) |
agents_max_count | null | Maximum nodes (autoscaler) |
Outputs: name, id, kube_config_raw, node_resource_group, aks_system_identity_principal_id
key-vault
| Variable | Default | Description |
|---|---|---|
sku_name | "standard" | standard or premium |
purge_protection_enabled | true | Prevent permanent deletion |
soft_delete_retention_days | 90 | Soft-delete window (7–90 days) |
enable_rbac_authorization | true | Use RBAC instead of access policies |
public_network_access_enabled | false | Block public access |
enable_private_endpoint | true | Deploy a private endpoint |
secrets | [] | List of secrets to create in the vault |
Outputs: id, vault_uri
acr
| Variable | Default | Description |
|---|---|---|
container_registry_config.sku | "Premium" | Basic, Standard, or Premium |
admin_enabled | false | Enable admin username/password |
public_network_access_enabled | false | Block public access |
encryption | true | Encrypt registry with a Key Vault key |
key_vault_id | null | Key Vault ID (when encryption = true) |
enable_private_endpoint | false | Deploy a private endpoint |
georeplications | [] | List of additional regions for replication |
Outputs: container_registry_id, container_registry_login_server, container_registry_identity_principal_id