Terraform on Azure -- Complete Guide to Provisioning Azure Infrastructure
Terraform on Azure enables you to define and provision the full Microsoft Azure infrastructure stack -- virtual networks, compute, databases, Kubernetes, and storage -- using declarative HCL configuration files version-controlled in Git.
What You'll Learn
In this tutorial, you will learn how to configure the Azure provider, provision virtual networks with subnets, deploy VMs with managed disks, create Azure SQL databases, set up AKS clusters, manage storage accounts with policies, and implement Azure RBAC with Terraform.
Why It Matters
Azure's portal-driven management does not scale for teams managing dozens of subscriptions and hundreds of resources. Terraform provides a unified declarative workflow across all Azure services, with plan preview, dependency management, and CI/CD integration.
Real-World Use
DodaTech uses Terraform to manage Azure infrastructure for enterprise clients alongside AWS deployments. Durga Antivirus Pro's threat intelligence pipeline runs on Azure AKS clusters provisioned entirely through Terraform, with Azure SQL for threat data storage and Blob Storage for malware samples.
Azure Provider Configuration
graph TD
A[Terraform Azure Config] --> B[AzureRM Provider]
B --> C[Virtual Network]
B --> D[Azure VM]
B --> E[Azure SQL]
B --> F[AKS]
B --> G[Storage Account]
B --> H[Azure AD / RBAC]
C --> I[Subnets]
C --> J[NSG]
C --> K[VPN Gateway]
D --> L[Managed Disks]
D --> M[Availability Sets]
F --> N[Node Pools]
F --> O[ACR Integration]
style A fill:#0078d4,color:#fff
style B fill:#0078d4,color:#fff
Provider Setup with Azure AD Authentication
# provider.tf
terraform {
required_version = ">= 1.6"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0"
}
}
}
provider "azurerm" {
features {
resource_group {
prevent_deletion_if_contains_resources = true
}
virtual_machine {
delete_os_disk_on_deletion = true
skip_shutdown_and_force_delete = false
}
}
subscription_id = var.subscription_id
use_oidc = true
}
# Login via Azure CLI (or OIDC in CI)
az login
terraform init
Expected output:
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/azurerm versions matching "~> 4.0"...
- Installing hashicorp/azurerm v4.3.0...
- Installed hashicorp/azurerm v4.3.0 (signed by HashiCorp)
Resource Group and Virtual Network
# networking.tf
resource "azurerm_resource_group" "main" {
name = "rg-${var.environment}-${var.location}"
location = var.location
tags = {
Environment = var.environment
ManagedBy = "Terraform"
}
}
resource "azurerm_virtual_network" "main" {
name = "vnet-${var.environment}"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
address_space = [var.vnet_cidr]
tags = {
Environment = var.environment
}
}
resource "azurerm_subnet" "public" {
name = "snet-${var.environment}-public"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = [var.public_subnet_cidr]
}
resource "azurerm_subnet" "private" {
name = "snet-${var.environment}-private"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = [var.private_subnet_cidr]
}
resource "azurerm_network_security_group" "web" {
name = "nsg-${var.environment}-web"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
security_rule {
name = "HTTP"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "80"
source_address_prefixes = ["*"]
destination_address_prefix = "*"
}
security_rule {
name = "HTTPS"
priority = 101
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "443"
source_address_prefixes = ["*"]
destination_address_prefix = "*"
}
}
Azure Kubernetes Service (AKS)
# aks.tf
resource "azurerm_kubernetes_cluster" "main" {
name = "aks-${var.environment}"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
dns_prefix = "dodatech-${var.environment}"
kubernetes_version = "1.30"
default_node_pool {
name = "system"
node_count = 2
vm_size = "Standard_D4s_v5"
enable_auto_scaling = true
min_count = 2
max_count = 5
vnet_subnet_id = azurerm_subnet.private.id
}
identity {
type = "SystemAssigned"
}
network_profile {
network_plugin = "azure"
network_policy = "calico"
service_cidr = "10.96.0.0/12"
dns_service_ip = "10.96.0.10"
}
azure_active_directory_role_based_access_control {
managed = true
azure_rbac_enabled = true
tenant_id = data.azurerm_client_config.current.tenant_id
}
oidc_issuer_enabled = true
workload_identity_enabled = true
tags = {
Environment = var.environment
}
}
terraform plan -out=aks.tfplan
terraform apply aks.tfplan
Expected output:
Apply complete! Resources: 4 added, 0 changed, 0 destroyed.
Outputs:
aks_cluster_name = "aks-production"
aks_kube_config = <sensitive>
Azure SQL Database
# database.tf
resource "azurerm_mssql_server" "main" {
name = "sql-${var.environment}-${var.location}"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
version = "12.0"
administrator_login = var.sql_admin_username
administrator_login_password = random_password.sql_admin.result
azuread_administrator {
login_username = data.azurerm_client_config.current.display_name
object_id = data.azurerm_client_config.current.object_id
}
tags = {
Environment = var.environment
}
}
resource "azurerm_mssql_database" "main" {
name = "dodatech-${var.environment}"
server_id = azurerm_mssql_server.main.id
collation = "SQL_Latin1_General_CP1_CI_AS"
license_type = "LicenseIncluded"
max_size_gb = var.environment == "production" ? 500 : 50
read_scale = var.environment == "production" ? true : false
sku_name = var.environment == "production" ? "GP_Gen5_8" : "GP_Gen5_2"
zone_redundant = var.environment == "production" ? true : false
tags = {
Environment = var.environment
}
}
Storage Account with Data Protection
# storage.tf
resource "azurerm_storage_account" "data" {
name = "stdodatech${var.environment}"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
account_tier = "Standard"
account_replication_type = var.environment == "production" ? "GRS" : "LRS"
account_kind = "StorageV2"
blob_properties {
versioning_enabled = true
delete_retention_policy {
days = 30
}
container_delete_retention_policy {
days = 30
}
}
network_rules {
default_action = "Deny"
bypass = ["AzureServices"]
ip_rules = var.allowed_admin_ips
}
tags = {
Environment = var.environment
}
}
resource "azurerm_storage_container" "malware_samples" {
name = "malware-samples"
storage_account_name = azurerm_storage_account.data.name
container_access_type = "private"
}
Azure RBAC with Terraform
# rbac.tf
data "azurerm_client_config" "current" {}
resource "azurerm_role_assignment" "aks_contributor" {
scope = azurerm_kubernetes_cluster.main.id
role_definition_name = "Azure Kubernetes Service Cluster User Role"
principal_id = data.azurerm_client_config.current.object_id
}
resource "azurerm_role_assignment" "acr_pull" {
scope = azurerm_container_registry.main.id
role_definition_name = "AcrPull"
principal_id = azurerm_kubernetes_cluster.main.kubelet_identity.0.object_id
}
Common Mistakes
1. Missing features Block
The features block in provider "azurerm" is required. Without it, Terraform cannot manage certain Azure resource behaviors.
2. Not Using OIDC Authentication
Using Azure service principal secrets in CI exposes credentials. Use OIDC with workload identity federation instead.
3. Forgetting Network Security Rules
Azure VNets are isolated by default. Missing NSG rules cause connectivity failures between services.
4. SQL Server Firewall Rules
Azure SQL blocks all traffic by default. Configure firewall_rule resources to allow application access.
5. AKS Node Pool Sizing
Undersized node pools cause scheduling failures in production. Use auto-scaling with minimum and maximum node counts.
Practice Questions
1. How do you authenticate the AzureRM provider with OIDC?
Set use_oidc = true in the provider block and configure workload identity federation in Azure AD.
2. What does the features block control in the AzureRM provider?
It configures resource behavior like VM disk deletion, resource group protection, and key vault soft-delete.
3. How do you enable Azure AD integration for AKS?
Set azure_active_directory_role_based_access_control with managed = true and azure_rbac_enabled = true in the AKS cluster resource.
4. Challenge: Write a Terraform configuration that provisions an AKS cluster with Azure AD integration, a private ACR, and workload identity for pod-to-Azure-service authentication.
Mini Project: Azure Three-Tier Architecture
Provision a virtual network with public and private subnets, an application gateway in the public subnet, an AKS cluster in the private subnet, Azure SQL Database with private endpoint, and a storage account with blob versioning. Use RBAC for least-privilege access.
Related Concepts
What's Next
Deploy Azure infrastructure with Terraform for your workloads, then explore GCP with Terraform for multi-cloud deployments. Study Cloud Computing strategies for hybrid cloud architectures.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro