Deploy AKS with Terraform: Azure Infrastructure as Code
Cloud

Deploy AKS with Terraform: Azure Infrastructure as Code

November 2, 202412 min readAzureTerraformAKS

Terraform + Azure is a powerful combination for deploying AKS in a repeatable way. VNet, managed identities, node pools, ACR integration: a complete guide with code examples.

Why Terraform for Azure?

Bicep is Azure's native IaC language, but Terraform has become the multi-cloud standard for many teams. Its main advantage: one tool to manage AWS, Azure, GCP, and third-party providers (Datadog, Cloudflare, GitHub). The azurerm provider, maintained by HashiCorp and Microsoft, is comprehensive and very active — over 700 supported resources.

This guide covers deploying a production-ready AKS cluster with Terraform: networking, identities, node pools, ACR, and a CI/CD pipeline.

Prerequisites: Service Principal and Backend

# Create a Service Principal for Terraform
az ad sp create-for-rbac   --name "sp-terraform-aks"   --role Contributor   --scopes /subscriptions/${SUBSCRIPTION_ID}   --json-auth

# Create Storage Account for Terraform backend
az group create --name rg-terraform-state --location francecentral
az storage account create   --name stterraformstate001   --resource-group rg-terraform-state   --sku Standard_LRS   --allow-blob-public-access false
az storage container create   --name tfstate   --account-name stterraformstate001

Terraform Project Structure

infra/
├── main.tf          # Providers + backend
├── variables.tf     # Input variables
├── outputs.tf       # Outputs (kubeconfig, etc.)
├── network.tf       # VNet, subnets, NSG
├── aks.tf           # AKS cluster + node pools
├── acr.tf           # Azure Container Registry
├── identity.tf      # Managed Identity + role assignments
└── environments/
    ├── dev.tfvars
    └── prd.tfvars

AKS Cluster Resource

resource "azurerm_kubernetes_cluster" "main" {
  name                = "aks-${var.environment}"
  resource_group_name = azurerm_resource_group.main.name
  location            = var.location
  dns_prefix          = "aks-${var.environment}"
  kubernetes_version  = var.kubernetes_version
  sku_tier            = "Standard"  # 99.95% SLA

  workload_identity_enabled = true
  oidc_issuer_enabled       = true

  default_node_pool {
    name                         = "system"
    vm_size                      = "Standard_D4s_v5"
    node_count                   = 3
    zones                        = ["1", "2", "3"]
    vnet_subnet_id               = azurerm_subnet.nodes.id
    pod_subnet_id                = azurerm_subnet.pods.id
    only_critical_addons_enabled = true
  }

  network_profile {
    network_plugin      = "azure"
    network_plugin_mode = "overlay"
    load_balancer_sku   = "standard"
  }

  auto_upgrade_profile {
    upgrade_channel = "patch"
  }

  azure_active_directory_role_based_access_control {
    managed            = true
    azure_rbac_enabled = true
  }
}

Spot Node Pool

resource "azurerm_kubernetes_cluster_node_pool" "spot" {
  name                  = "spot"
  kubernetes_cluster_id = azurerm_kubernetes_cluster.main.id
  vm_size               = "Standard_D4s_v5"
  priority              = "Spot"
  eviction_policy       = "Delete"
  spot_max_price        = -1
  node_count            = 0
  min_count             = 0
  max_count             = 20
  enable_auto_scaling   = true

  node_taints = ["kubernetes.azure.com/scalesetpriority=spot:NoSchedule"]
}

CI/CD Pipeline with GitHub Actions (OIDC)

name: Terraform AKS Deploy
on:
  push:
    branches: [main]
    paths: ["infra/**"]

permissions:
  id-token: write
  contents: read

jobs:
  terraform:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4
      - uses: azure/login@v2
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
      - uses: hashicorp/setup-terraform@v3
      - run: terraform init && terraform apply -auto-approve -var-file=environments/prd.tfvars
        working-directory: infra

Best Practices and Pitfalls

  • Never commit state: always use a remote backend (Azure Storage, Terraform Cloud)
  • Pin versions: provider ~> 3.110 and Terraform >= 1.6 in required_providers
  • Separate node pools from the main cluster to avoid recreating the cluster on updates
  • Avoid lifecycle { ignore_changes } on node_count when auto-scaling is enabled
  • Use .tfvars files per environment rather than Git branches

Conclusion

Terraform on Azure enables deploying a complete, secure, and repeatable AKS cluster in a few hundred lines of code. The combination of a custom VNet + Azure CNI Overlay + Workload Identity + Managed Prometheus is the production reference in 2025.

Move2Cloud supports its clients in setting up this IaC stack, from architecture design to CI/CD integration, with reusable Terraform modules and multi-environment governance.

← Back to blog