Infrastructure as Code18 min read1,790 lines

Learning state

Track this guide

Saved in this browser only. No account required.

Terraform with Azure Master Class

Engineering-Grade Reference Manual for Terraform & Microsoft Azure
A comprehensive guide to Infrastructure as Code with Terraform, focused on Azure cloud resources


Table of Contents

  1. Terraform Fundamentals
  2. Azure Provider Setup
  3. Core Terraform Commands
  4. Azure Resource Groups
  5. Azure Virtual Networks
  6. Azure Virtual Machines
  7. Azure Storage
  8. Azure Kubernetes Service (AKS)
  9. Azure App Service
  10. Azure Database Services
  11. State Management
  12. Modules & Reusability
  13. Variables & Outputs
  14. Workspaces & Environments
  15. Security & Best Practices

Terraform Fundamentals

๐Ÿ”น What is Terraform?

Infrastructure as Code (IaC) tool that allows you to define and provision infrastructure using declarative configuration files.

Key Concepts:

  • Providers - Plugins for cloud platforms (Azure, AWS, GCP)
  • Resources - Infrastructure components (VMs, networks, databases)
  • State - Current state of infrastructure
  • Modules - Reusable configuration packages
  • Variables - Parameterized configurations
  • Outputs - Values exposed after apply

Terraform Workflow:

Write โ†’ Plan โ†’ Apply โ†’ Manage

๐Ÿ”น Installation

โœ”๏ธ Install Terraform

# macOS (Homebrew)
brew tap hashicorp/tap
brew install hashicorp/tap/terraform

# Verify installation
terraform version

# Enable tab completion
terraform -install-autocomplete

โœ”๏ธ Install Azure CLI

# macOS
brew install azure-cli

# Login to Azure
az login

# Set subscription
az account set --subscription "Your Subscription Name"

# Verify
az account show

Azure Provider Setup

๐Ÿ”น Provider Configuration

โœ”๏ธ Basic Provider Setup

# main.tf
terraform {
  required_version = ">= 1.0"
  
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.0"
    }
  }
}

provider "azurerm" {
  features {}
}

โœ”๏ธ Provider with Specific Features

provider "azurerm" {
  features {
    resource_group {
      prevent_deletion_if_contains_resources = true
    }
    
    key_vault {
      purge_soft_delete_on_destroy    = true
      recover_soft_deleted_key_vaults = true
    }
    
    virtual_machine {
      delete_os_disk_on_deletion     = true
      graceful_shutdown              = false
      skip_shutdown_and_force_delete = false
    }
  }
  
  subscription_id = var.subscription_id
  tenant_id       = var.tenant_id
}

โœ”๏ธ Authentication Methods

Azure CLI (Recommended for local development):

az login

Service Principal (Recommended for CI/CD):

provider "azurerm" {
  features {}
  
  subscription_id = var.subscription_id
  client_id       = var.client_id
  client_secret   = var.client_secret
  tenant_id       = var.tenant_id
}

Managed Identity (For Azure VMs):

provider "azurerm" {
  features {}
  use_msi = true
}

Core Terraform Commands

๐Ÿ”น Command: terraform init

โœ”๏ธ What It Does
Initializes Terraform working directory, downloads providers, and sets up backend.

โœ”๏ธ Syntax Examples

# Basic: Initialize directory
terraform init

# Real-world: Reinitialize with upgrade
terraform init -upgrade

# Production: Initialize with backend config
terraform init -backend-config="storage_account_name=mystorageacct"

# Reconfigure backend
terraform init -reconfigure

# Migrate state
terraform init -migrate-state

โœ”๏ธ Notes, Tips & Common Mistakes

  • Run after changing provider versions
  • -upgrade updates providers to latest allowed version
  • Required before other Terraform commands
  • Creates .terraform directory and lock file

๐Ÿ”น Command: terraform plan

โœ”๏ธ What It Does
Creates execution plan showing what Terraform will do without making changes.

โœ”๏ธ Syntax Examples

# Basic: Show plan
terraform plan

# Real-world: Save plan to file
terraform plan -out=tfplan

# Production: Plan with variable file
terraform plan -var-file="production.tfvars"

# Plan with specific target
terraform plan -target=azurerm_virtual_machine.example

# Detailed plan output
terraform plan -json

# Destroy plan
terraform plan -destroy

โœ”๏ธ Notes, Tips & Common Mistakes

  • Always run before apply
  • -out saves plan for exact apply
  • Review plan carefully before applying
  • Use -target for selective planning (avoid in production)

๐Ÿ”น Command: terraform apply

โœ”๏ธ What It Does
Applies changes to reach desired state defined in configuration.

โœ”๏ธ Syntax Examples

# Basic: Apply with confirmation
terraform apply

# Real-world: Auto-approve (CI/CD)
terraform apply -auto-approve

# Production: Apply saved plan
terraform apply tfplan

# Apply with variables
terraform apply -var="environment=production"

# Apply with var file
terraform apply -var-file="production.tfvars"

# Targeted apply
terraform apply -target=azurerm_resource_group.example

โœ”๏ธ Notes, Tips & Common Mistakes

  • Creates/updates/deletes resources
  • Use -auto-approve only in automation
  • Apply saved plans for consistency
  • Review plan output before confirming

๐Ÿ”น Command: terraform destroy

โœ”๏ธ What It Does
Destroys all resources managed by Terraform. Use with extreme caution!

โœ”๏ธ Syntax Examples

# Basic: Destroy with confirmation
terraform destroy

# Real-world: Auto-approve destroy
terraform destroy -auto-approve

# Production: Destroy specific resource
terraform destroy -target=azurerm_virtual_machine.example

# Destroy with var file
terraform destroy -var-file="production.tfvars"

โœ”๏ธ Notes, Tips & Common Mistakes

  • DANGEROUS - permanently deletes resources
  • Always verify you're in correct workspace/environment
  • Use -target to destroy specific resources
  • Consider terraform plan -destroy first

๐Ÿ”น Command: terraform fmt

โœ”๏ธ What It Does
Formats Terraform configuration files to canonical style.

โœ”๏ธ Syntax Examples

# Basic: Format current directory
terraform fmt

# Real-world: Format recursively
terraform fmt -recursive

# Production: Check formatting (CI/CD)
terraform fmt -check

# Show diff
terraform fmt -diff

๐Ÿ”น Command: terraform validate

โœ”๏ธ What It Does
Validates configuration syntax and internal consistency.

โœ”๏ธ Syntax Examples

# Basic: Validate configuration
terraform validate

# Real-world: JSON output for CI/CD
terraform validate -json

๐Ÿ”น Command: terraform show

โœ”๏ธ What It Does
Shows current state or saved plan in human-readable format.

โœ”๏ธ Syntax Examples

# Basic: Show current state
terraform show

# Real-world: Show saved plan
terraform show tfplan

# Production: JSON output
terraform show -json

๐Ÿ”น Command: terraform output

โœ”๏ธ What It Does
Displays output values from state file.

โœ”๏ธ Syntax Examples

# Basic: Show all outputs
terraform output

# Real-world: Show specific output
terraform output resource_group_name

# Production: JSON format
terraform output -json

# Raw output (no quotes)
terraform output -raw public_ip

Azure Resource Groups

๐Ÿ”น Resource Group Basics

โœ”๏ธ Simple Resource Group

# resource-group.tf
resource "azurerm_resource_group" "example" {
  name     = "rg-myapp-prod-eastus"
  location = "East US"
  
  tags = {
    Environment = "Production"
    ManagedBy   = "Terraform"
    Project     = "MyApp"
  }
}

โœ”๏ธ Resource Group with Variables

# variables.tf
variable "environment" {
  description = "Environment name"
  type        = string
  default     = "dev"
}

variable "location" {
  description = "Azure region"
  type        = string
  default     = "East US"
}

variable "project_name" {
  description = "Project name"
  type        = string
}

# resource-group.tf
resource "azurerm_resource_group" "main" {
  name     = "rg-${var.project_name}-${var.environment}-${replace(lower(var.location), " ", "")}"
  location = var.location
  
  tags = {
    Environment = var.environment
    ManagedBy   = "Terraform"
    Project     = var.project_name
  }
}

โœ”๏ธ Naming Convention Best Practices

Resource Type - Project - Environment - Region

Examples:
rg-myapp-prod-eastus          (Resource Group)
vnet-myapp-prod-eastus        (Virtual Network)
vm-myapp-web-prod-eastus-01   (Virtual Machine)
st-myapp-prod-eastus          (Storage Account - max 24 chars)
kv-myapp-prod-eastus          (Key Vault)

Azure Virtual Networks

๐Ÿ”น Virtual Network Configuration

โœ”๏ธ Basic VNet with Subnets

# network.tf
resource "azurerm_virtual_network" "main" {
  name                = "vnet-${var.project_name}-${var.environment}"
  location            = azurerm_resource_group.main.location
  resource_group_name = azurerm_resource_group.main.name
  address_space       = ["10.0.0.0/16"]
  
  tags = {
    Environment = var.environment
  }
}

resource "azurerm_subnet" "web" {
  name                 = "subnet-web"
  resource_group_name  = azurerm_resource_group.main.name
  virtual_network_name = azurerm_virtual_network.main.name
  address_prefixes     = ["10.0.1.0/24"]
}

resource "azurerm_subnet" "app" {
  name                 = "subnet-app"
  resource_group_name  = azurerm_resource_group.main.name
  virtual_network_name = azurerm_virtual_network.main.name
  address_prefixes     = ["10.0.2.0/24"]
}

resource "azurerm_subnet" "data" {
  name                 = "subnet-data"
  resource_group_name  = azurerm_resource_group.main.name
  virtual_network_name = azurerm_virtual_network.main.name
  address_prefixes     = ["10.0.3.0/24"]
}

โœ”๏ธ Network Security Group

resource "azurerm_network_security_group" "web" {
  name                = "nsg-web-${var.environment}"
  location            = azurerm_resource_group.main.location
  resource_group_name = azurerm_resource_group.main.name
  
  security_rule {
    name                       = "AllowHTTP"
    priority                   = 100
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "80"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
  
  security_rule {
    name                       = "AllowHTTPS"
    priority                   = 110
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "443"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
  
  security_rule {
    name                       = "AllowSSH"
    priority                   = 120
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "22"
    source_address_prefix      = var.admin_ip
    destination_address_prefix = "*"
  }
}

resource "azurerm_subnet_network_security_group_association" "web" {
  subnet_id                 = azurerm_subnet.web.id
  network_security_group_id = azurerm_network_security_group.web.id
}

โœ”๏ธ Public IP Address

resource "azurerm_public_ip" "example" {
  name                = "pip-${var.project_name}-${var.environment}"
  location            = azurerm_resource_group.main.location
  resource_group_name = azurerm_resource_group.main.name
  allocation_method   = "Static"
  sku                 = "Standard"
  
  tags = {
    Environment = var.environment
  }
}

Azure Virtual Machines

๐Ÿ”น Linux Virtual Machine

โœ”๏ธ Complete VM Configuration

# vm.tf
resource "azurerm_network_interface" "main" {
  name                = "nic-${var.vm_name}"
  location            = azurerm_resource_group.main.location
  resource_group_name = azurerm_resource_group.main.name
  
  ip_configuration {
    name                          = "internal"
    subnet_id                     = azurerm_subnet.web.id
    private_ip_address_allocation = "Dynamic"
    public_ip_address_id          = azurerm_public_ip.example.id
  }
}

resource "azurerm_linux_virtual_machine" "main" {
  name                = "vm-${var.vm_name}"
  location            = azurerm_resource_group.main.location
  resource_group_name = azurerm_resource_group.main.name
  size                = "Standard_B2s"
  admin_username      = "azureuser"
  
  network_interface_ids = [
    azurerm_network_interface.main.id,
  ]
  
  admin_ssh_key {
    username   = "azureuser"
    public_key = file("~/.ssh/id_rsa.pub")
  }
  
  os_disk {
    name                 = "osdisk-${var.vm_name}"
    caching              = "ReadWrite"
    storage_account_type = "Premium_LRS"
  }
  
  source_image_reference {
    publisher = "Canonical"
    offer     = "0001-com-ubuntu-server-jammy"
    sku       = "22_04-lts-gen2"
    version   = "latest"
  }
  
  custom_data = base64encode(file("cloud-init.yaml"))
  
  tags = {
    Environment = var.environment
  }
}

โœ”๏ธ Cloud-Init Configuration

# cloud-init.yaml
#cloud-config
package_update: true
package_upgrade: true

packages:
  - docker.io
  - nginx
  - git

runcmd:
  - systemctl start docker
  - systemctl enable docker
  - usermod -aG docker azureuser
  - systemctl start nginx
  - systemctl enable nginx

โœ”๏ธ VM Scale Set

resource "azurerm_linux_virtual_machine_scale_set" "main" {
  name                = "vmss-${var.project_name}-${var.environment}"
  location            = azurerm_resource_group.main.location
  resource_group_name = azurerm_resource_group.main.name
  sku                 = "Standard_B2s"
  instances           = 3
  admin_username      = "azureuser"
  
  admin_ssh_key {
    username   = "azureuser"
    public_key = file("~/.ssh/id_rsa.pub")
  }
  
  source_image_reference {
    publisher = "Canonical"
    offer     = "0001-com-ubuntu-server-jammy"
    sku       = "22_04-lts-gen2"
    version   = "latest"
  }
  
  os_disk {
    storage_account_type = "Standard_LRS"
    caching              = "ReadWrite"
  }
  
  network_interface {
    name    = "nic-vmss"
    primary = true
    
    ip_configuration {
      name      = "internal"
      primary   = true
      subnet_id = azurerm_subnet.web.id
      
      load_balancer_backend_address_pool_ids = [
        azurerm_lb_backend_address_pool.main.id
      ]
    }
  }
  
  automatic_os_upgrade_policy {
    disable_automatic_rollback  = false
    enable_automatic_os_upgrade = true
  }
  
  rolling_upgrade_policy {
    max_batch_instance_percent              = 20
    max_unhealthy_instance_percent          = 20
    max_unhealthy_upgraded_instance_percent = 20
    pause_time_between_batches              = "PT0S"
  }
}

Azure Storage

๐Ÿ”น Storage Account

โœ”๏ธ Basic Storage Account

resource "azurerm_storage_account" "main" {
  name                     = "st${var.project_name}${var.environment}"
  resource_group_name      = azurerm_resource_group.main.name
  location                 = azurerm_resource_group.main.location
  account_tier             = "Standard"
  account_replication_type = "LRS"
  
  tags = {
    Environment = var.environment
  }
}

โœ”๏ธ Storage Account with Advanced Features

resource "azurerm_storage_account" "advanced" {
  name                     = "st${var.project_name}${var.environment}"
  resource_group_name      = azurerm_resource_group.main.name
  location                 = azurerm_resource_group.main.location
  account_tier             = "Standard"
  account_replication_type = "GRS"
  account_kind             = "StorageV2"
  
  enable_https_traffic_only       = true
  min_tls_version                 = "TLS1_2"
  allow_nested_items_to_be_public = false
  
  blob_properties {
    versioning_enabled = true
    
    delete_retention_policy {
      days = 30
    }
    
    container_delete_retention_policy {
      days = 30
    }
  }
  
  network_rules {
    default_action             = "Deny"
    ip_rules                   = [var.admin_ip]
    virtual_network_subnet_ids = [azurerm_subnet.app.id]
    bypass                     = ["AzureServices"]
  }
  
  tags = {
    Environment = var.environment
  }
}

โœ”๏ธ Blob Container

resource "azurerm_storage_container" "data" {
  name                  = "data"
  storage_account_name  = azurerm_storage_account.main.name
  container_access_type = "private"
}

โœ”๏ธ File Share

resource "azurerm_storage_share" "files" {
  name                 = "fileshare"
  storage_account_name = azurerm_storage_account.main.name
  quota                = 50
}

Azure Kubernetes Service (AKS)

๐Ÿ”น AKS Cluster

โœ”๏ธ Production-Ready AKS Cluster

resource "azurerm_kubernetes_cluster" "main" {
  name                = "aks-${var.project_name}-${var.environment}"
  location            = azurerm_resource_group.main.location
  resource_group_name = azurerm_resource_group.main.name
  dns_prefix          = "aks-${var.project_name}"
  kubernetes_version  = "1.27.3"
  
  default_node_pool {
    name                = "system"
    node_count          = 3
    vm_size             = "Standard_D2s_v3"
    type                = "VirtualMachineScaleSets"
    availability_zones  = ["1", "2", "3"]
    enable_auto_scaling = true
    min_count           = 3
    max_count           = 10
    
    vnet_subnet_id = azurerm_subnet.aks.id
    
    upgrade_settings {
      max_surge = "33%"
    }
  }
  
  identity {
    type = "SystemAssigned"
  }
  
  network_profile {
    network_plugin    = "azure"
    network_policy    = "azure"
    load_balancer_sku = "standard"
    service_cidr      = "10.1.0.0/16"
    dns_service_ip    = "10.1.0.10"
  }
  
  azure_active_directory_role_based_access_control {
    managed                = true
    azure_rbac_enabled     = true
    admin_group_object_ids = [var.admin_group_id]
  }
  
  oms_agent {
    log_analytics_workspace_id = azurerm_log_analytics_workspace.main.id
  }
  
  tags = {
    Environment = var.environment
  }
}

# Additional Node Pool
resource "azurerm_kubernetes_cluster_node_pool" "user" {
  name                  = "user"
  kubernetes_cluster_id = azurerm_kubernetes_cluster.main.id
  vm_size               = "Standard_D4s_v3"
  node_count            = 3
  enable_auto_scaling   = true
  min_count             = 3
  max_count             = 20
  
  vnet_subnet_id = azurerm_subnet.aks.id
  
  node_labels = {
    "workload" = "applications"
  }
  
  tags = {
    Environment = var.environment
  }
}

โœ”๏ธ Container Registry

resource "azurerm_container_registry" "main" {
  name                = "acr${var.project_name}${var.environment}"
  resource_group_name = azurerm_resource_group.main.name
  location            = azurerm_resource_group.main.location
  sku                 = "Premium"
  admin_enabled       = false
  
  georeplications {
    location = "West US"
    tags     = {}
  }
  
  network_rule_set {
    default_action = "Deny"
    
    ip_rule {
      action   = "Allow"
      ip_range = var.admin_ip
    }
  }
}

# Attach ACR to AKS
resource "azurerm_role_assignment" "aks_acr" {
  principal_id                     = azurerm_kubernetes_cluster.main.kubelet_identity[0].object_id
  role_definition_name             = "AcrPull"
  scope                            = azurerm_container_registry.main.id
  skip_service_principal_aad_check = true
}

Azure App Service

๐Ÿ”น App Service Plan & Web App

โœ”๏ธ Linux App Service

resource "azurerm_service_plan" "main" {
  name                = "asp-${var.project_name}-${var.environment}"
  location            = azurerm_resource_group.main.location
  resource_group_name = azurerm_resource_group.main.name
  os_type             = "Linux"
  sku_name            = "P1v3"
}

resource "azurerm_linux_web_app" "main" {
  name                = "app-${var.project_name}-${var.environment}"
  location            = azurerm_resource_group.main.location
  resource_group_name = azurerm_resource_group.main.name
  service_plan_id     = azurerm_service_plan.main.id
  
  site_config {
    always_on = true
    
    application_stack {
      node_version = "18-lts"
    }
    
    health_check_path = "/health"
  }
  
  app_settings = {
    "WEBSITE_NODE_DEFAULT_VERSION" = "18-lts"
    "NODE_ENV"                     = var.environment
    "DATABASE_URL"                 = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.db_url.id})"
  }
  
  identity {
    type = "SystemAssigned"
  }
  
  logs {
    application_logs {
      file_system_level = "Information"
    }
    
    http_logs {
      file_system {
        retention_in_days = 7
        retention_in_mb   = 35
      }
    }
  }
  
  tags = {
    Environment = var.environment
  }
}

โœ”๏ธ Container-Based App Service

resource "azurerm_linux_web_app" "container" {
  name                = "app-${var.project_name}-container-${var.environment}"
  location            = azurerm_resource_group.main.location
  resource_group_name = azurerm_resource_group.main.name
  service_plan_id     = azurerm_service_plan.main.id
  
  site_config {
    always_on = true
    
    application_stack {
      docker_image     = "${azurerm_container_registry.main.login_server}/myapp"
      docker_image_tag = "latest"
    }
  }
  
  app_settings = {
    "DOCKER_REGISTRY_SERVER_URL"      = "https://${azurerm_container_registry.main.login_server}"
    "DOCKER_REGISTRY_SERVER_USERNAME" = azurerm_container_registry.main.admin_username
    "DOCKER_REGISTRY_SERVER_PASSWORD" = azurerm_container_registry.main.admin_password
  }
  
  identity {
    type = "SystemAssigned"
  }
}

Azure Database Services

๐Ÿ”น Azure SQL Database

โœ”๏ธ SQL Server & Database

resource "azurerm_mssql_server" "main" {
  name                         = "sql-${var.project_name}-${var.environment}"
  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 = var.sql_admin_password
  
  azuread_administrator {
    login_username = var.aad_admin_username
    object_id      = var.aad_admin_object_id
  }
  
  tags = {
    Environment = var.environment
  }
}

resource "azurerm_mssql_database" "main" {
  name           = "sqldb-${var.project_name}-${var.environment}"
  server_id      = azurerm_mssql_server.main.id
  collation      = "SQL_Latin1_General_CP1_CI_AS"
  license_type   = "LicenseIncluded"
  sku_name       = "S1"
  zone_redundant = false
  
  short_term_retention_policy {
    retention_days = 7
  }
  
  long_term_retention_policy {
    weekly_retention  = "P1W"
    monthly_retention = "P1M"
    yearly_retention  = "P1Y"
    week_of_year      = 1
  }
  
  tags = {
    Environment = var.environment
  }
}

resource "azurerm_mssql_firewall_rule" "allow_azure" {
  name             = "AllowAzureServices"
  server_id        = azurerm_mssql_server.main.id
  start_ip_address = "0.0.0.0"
  end_ip_address   = "0.0.0.0"
}

โœ”๏ธ PostgreSQL Flexible Server

resource "azurerm_postgresql_flexible_server" "main" {
  name                   = "psql-${var.project_name}-${var.environment}"
  resource_group_name    = azurerm_resource_group.main.name
  location               = azurerm_resource_group.main.location
  version                = "15"
  administrator_login    = var.db_admin_username
  administrator_password = var.db_admin_password
  
  storage_mb   = 32768
  sku_name     = "GP_Standard_D2s_v3"
  zone         = "1"
  
  backup_retention_days        = 7
  geo_redundant_backup_enabled = false
  
  high_availability {
    mode                      = "ZoneRedundant"
    standby_availability_zone = "2"
  }
  
  tags = {
    Environment = var.environment
  }
}

resource "azurerm_postgresql_flexible_server_database" "main" {
  name      = "myapp"
  server_id = azurerm_postgresql_flexible_server.main.id
  collation = "en_US.utf8"
  charset   = "utf8"
}

โœ”๏ธ Cosmos DB

resource "azurerm_cosmosdb_account" "main" {
  name                = "cosmos-${var.project_name}-${var.environment}"
  location            = azurerm_resource_group.main.location
  resource_group_name = azurerm_resource_group.main.name
  offer_type          = "Standard"
  kind                = "GlobalDocumentDB"
  
  consistency_policy {
    consistency_level       = "Session"
    max_interval_in_seconds = 5
    max_staleness_prefix    = 100
  }
  
  geo_location {
    location          = azurerm_resource_group.main.location
    failover_priority = 0
  }
  
  geo_location {
    location          = "West US"
    failover_priority = 1
  }
  
  backup {
    type                = "Periodic"
    interval_in_minutes = 240
    retention_in_hours  = 8
  }
  
  tags = {
    Environment = var.environment
  }
}

resource "azurerm_cosmosdb_sql_database" "main" {
  name                = "myapp-db"
  resource_group_name = azurerm_resource_group.main.name
  account_name        = azurerm_cosmosdb_account.main.name
  throughput          = 400
}

State Management

๐Ÿ”น Remote State with Azure Storage

โœ”๏ธ Backend Configuration

# backend.tf
terraform {
  backend "azurerm" {
    resource_group_name  = "rg-terraform-state"
    storage_account_name = "sttfstate12345"
    container_name       = "tfstate"
    key                  = "production.terraform.tfstate"
  }
}

โœ”๏ธ Create State Storage (One-time setup)

# Create resource group
az group create --name rg-terraform-state --location eastus

# Create storage account
az storage account create \
  --name sttfstate12345 \
  --resource-group rg-terraform-state \
  --location eastus \
  --sku Standard_LRS \
  --encryption-services blob

# Create container
az storage container create \
  --name tfstate \
  --account-name sttfstate12345

โœ”๏ธ State Locking

Azure Storage automatically provides state locking using blob leases.

โœ”๏ธ State Commands

# List state resources
terraform state list

# Show specific resource
terraform state show azurerm_resource_group.main

# Move resource in state
terraform state mv azurerm_resource_group.old azurerm_resource_group.new

# Remove resource from state (doesn't delete resource)
terraform state rm azurerm_resource_group.example

# Pull remote state
terraform state pull

# Push local state
terraform state push

Modules & Reusability

๐Ÿ”น Creating Modules

โœ”๏ธ Module Structure

modules/
  โ””โ”€โ”€ azure-vm/
      โ”œโ”€โ”€ main.tf
      โ”œโ”€โ”€ variables.tf
      โ”œโ”€โ”€ outputs.tf
      โ””โ”€โ”€ README.md

โœ”๏ธ Module Example: Azure VM

# modules/azure-vm/variables.tf
variable "resource_group_name" {
  description = "Resource group name"
  type        = string
}

variable "location" {
  description = "Azure region"
  type        = string
}

variable "vm_name" {
  description = "Virtual machine name"
  type        = string
}

variable "vm_size" {
  description = "VM size"
  type        = string
  default     = "Standard_B2s"
}

variable "subnet_id" {
  description = "Subnet ID"
  type        = string
}

variable "admin_username" {
  description = "Admin username"
  type        = string
  default     = "azureuser"
}

variable "ssh_public_key" {
  description = "SSH public key"
  type        = string
}

# modules/azure-vm/main.tf
resource "azurerm_network_interface" "main" {
  name                = "nic-${var.vm_name}"
  location            = var.location
  resource_group_name = var.resource_group_name
  
  ip_configuration {
    name                          = "internal"
    subnet_id                     = var.subnet_id
    private_ip_address_allocation = "Dynamic"
  }
}

resource "azurerm_linux_virtual_machine" "main" {
  name                = var.vm_name
  location            = var.location
  resource_group_name = var.resource_group_name
  size                = var.vm_size
  admin_username      = var.admin_username
  
  network_interface_ids = [
    azurerm_network_interface.main.id,
  ]
  
  admin_ssh_key {
    username   = var.admin_username
    public_key = var.ssh_public_key
  }
  
  os_disk {
    caching              = "ReadWrite"
    storage_account_type = "Premium_LRS"
  }
  
  source_image_reference {
    publisher = "Canonical"
    offer     = "0001-com-ubuntu-server-jammy"
    sku       = "22_04-lts-gen2"
    version   = "latest"
  }
}

# modules/azure-vm/outputs.tf
output "vm_id" {
  description = "Virtual machine ID"
  value       = azurerm_linux_virtual_machine.main.id
}

output "private_ip" {
  description = "Private IP address"
  value       = azurerm_network_interface.main.private_ip_address
}

โœ”๏ธ Using Modules

# main.tf
module "web_vm" {
  source = "./modules/azure-vm"
  
  resource_group_name = azurerm_resource_group.main.name
  location            = azurerm_resource_group.main.location
  vm_name             = "vm-web-prod-01"
  vm_size             = "Standard_D2s_v3"
  subnet_id           = azurerm_subnet.web.id
  ssh_public_key      = file("~/.ssh/id_rsa.pub")
}

module "app_vm" {
  source = "./modules/azure-vm"
  
  resource_group_name = azurerm_resource_group.main.name
  location            = azurerm_resource_group.main.location
  vm_name             = "vm-app-prod-01"
  vm_size             = "Standard_D4s_v3"
  subnet_id           = azurerm_subnet.app.id
  ssh_public_key      = file("~/.ssh/id_rsa.pub")
}

# Access module outputs
output "web_vm_ip" {
  value = module.web_vm.private_ip
}

Variables & Outputs

๐Ÿ”น Variable Types

โœ”๏ธ Variable Definitions

# variables.tf

# String
variable "environment" {
  description = "Environment name"
  type        = string
  default     = "dev"
}

# Number
variable "instance_count" {
  description = "Number of instances"
  type        = number
  default     = 2
}

# Bool
variable "enable_monitoring" {
  description = "Enable monitoring"
  type        = bool
  default     = true
}

# List
variable "allowed_ips" {
  description = "Allowed IP addresses"
  type        = list(string)
  default     = ["10.0.0.0/8"]
}

# Map
variable "tags" {
  description = "Resource tags"
  type        = map(string)
  default = {
    ManagedBy = "Terraform"
    Project   = "MyApp"
  }
}

# Object
variable "vm_config" {
  description = "VM configuration"
  type = object({
    size     = string
    os_disk  = string
    data_disks = list(number)
  })
  default = {
    size       = "Standard_B2s"
    os_disk    = "Premium_LRS"
    data_disks = [128, 256]
  }
}

# Sensitive
variable "db_password" {
  description = "Database password"
  type        = string
  sensitive   = true
}

โœ”๏ธ Variable Files

# terraform.tfvars (auto-loaded)
environment      = "production"
instance_count   = 5
enable_monitoring = true
allowed_ips      = ["203.0.113.0/24", "198.51.100.0/24"]

tags = {
  Environment = "Production"
  ManagedBy   = "Terraform"
  CostCenter  = "Engineering"
}
# production.tfvars (load with -var-file)
environment = "production"
location    = "East US"
vm_size     = "Standard_D4s_v3"

โœ”๏ธ Output Values

# outputs.tf
output "resource_group_name" {
  description = "Resource group name"
  value       = azurerm_resource_group.main.name
}

output "public_ip_address" {
  description = "Public IP address"
  value       = azurerm_public_ip.example.ip_address
}

output "connection_string" {
  description = "Database connection string"
  value       = azurerm_postgresql_flexible_server.main.fqdn
  sensitive   = true
}

output "vm_ids" {
  description = "Virtual machine IDs"
  value       = {
    web = module.web_vm.vm_id
    app = module.app_vm.vm_id
  }
}

Workspaces & Environments

๐Ÿ”น Terraform Workspaces

โœ”๏ธ Workspace Commands

# List workspaces
terraform workspace list

# Create new workspace
terraform workspace new production

# Switch workspace
terraform workspace select production

# Show current workspace
terraform workspace show

# Delete workspace
terraform workspace delete staging

โœ”๏ธ Using Workspaces in Configuration

locals {
  environment = terraform.workspace
  
  vm_sizes = {
    dev     = "Standard_B2s"
    staging = "Standard_D2s_v3"
    prod    = "Standard_D4s_v3"
  }
  
  vm_size = local.vm_sizes[local.environment]
}

resource "azurerm_resource_group" "main" {
  name     = "rg-myapp-${local.environment}"
  location = var.location
  
  tags = {
    Environment = local.environment
  }
}

Security & Best Practices

โœ… Security Best Practices

โœ”๏ธ Use Azure Key Vault for Secrets

resource "azurerm_key_vault" "main" {
  name                = "kv-${var.project_name}-${var.environment}"
  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            = "premium"
  
  purge_protection_enabled   = true
  soft_delete_retention_days = 90
  
  network_acls {
    default_action = "Deny"
    bypass         = "AzureServices"
    ip_rules       = [var.admin_ip]
  }
}

resource "azurerm_key_vault_secret" "db_password" {
  name         = "db-password"
  value        = var.db_password
  key_vault_id = azurerm_key_vault.main.id
}

# Grant App Service access
resource "azurerm_key_vault_access_policy" "app" {
  key_vault_id = azurerm_key_vault.main.id
  tenant_id    = data.azurerm_client_config.current.tenant_id
  object_id    = azurerm_linux_web_app.main.identity[0].principal_id
  
  secret_permissions = [
    "Get",
    "List"
  ]
}

โœ”๏ธ Use Managed Identities

resource "azurerm_linux_web_app" "main" {
  # ... other config ...
  
  identity {
    type = "SystemAssigned"
  }
}

# Grant permissions
resource "azurerm_role_assignment" "app_storage" {
  scope                = azurerm_storage_account.main.id
  role_definition_name = "Storage Blob Data Contributor"
  principal_id         = azurerm_linux_web_app.main.identity[0].principal_id
}

โœ”๏ธ Enable Diagnostic Logging

resource "azurerm_log_analytics_workspace" "main" {
  name                = "log-${var.project_name}-${var.environment}"
  location            = azurerm_resource_group.main.location
  resource_group_name = azurerm_resource_group.main.name
  sku                 = "PerGB2018"
  retention_in_days   = 30
}

resource "azurerm_monitor_diagnostic_setting" "vm" {
  name                       = "diag-vm"
  target_resource_id         = azurerm_linux_virtual_machine.main.id
  log_analytics_workspace_id = azurerm_log_analytics_workspace.main.id
  
  metric {
    category = "AllMetrics"
    enabled  = true
  }
}

โœ… Terraform Best Practices

1. Use Remote State

terraform {
  backend "azurerm" {
    resource_group_name  = "rg-terraform-state"
    storage_account_name = "sttfstate"
    container_name       = "tfstate"
    key                  = "prod.tfstate"
  }
}

2. Use Variables for Everything

# Don't hardcode values
resource "azurerm_resource_group" "bad" {
  name     = "rg-myapp-prod"  # Bad
  location = "East US"         # Bad
}

# Use variables
resource "azurerm_resource_group" "good" {
  name     = "rg-${var.project_name}-${var.environment}"
  location = var.location
}

3. Use Data Sources

data "azurerm_client_config" "current" {}

data "azurerm_resource_group" "existing" {
  name = "rg-existing"
}

4. Use Locals for Computed Values

locals {
  common_tags = {
    Environment = var.environment
    ManagedBy   = "Terraform"
    Project     = var.project_name
  }
  
  resource_prefix = "${var.project_name}-${var.environment}"
}

5. Use Lifecycle Rules

resource "azurerm_resource_group" "main" {
  name     = "rg-${var.project_name}"
  location = var.location
  
  lifecycle {
    prevent_destroy = true
    ignore_changes  = [tags]
  }
}

6. Use Depends_on Sparingly

# Terraform infers dependencies automatically
# Only use depends_on for hidden dependencies

resource "azurerm_role_assignment" "example" {
  # ... config ...
  
  depends_on = [azurerm_key_vault_access_policy.app]
}

7. Organize Files Logically

terraform/
  โ”œโ”€โ”€ main.tf              # Main resources
  โ”œโ”€โ”€ variables.tf         # Variable definitions
  โ”œโ”€โ”€ outputs.tf           # Output definitions
  โ”œโ”€โ”€ backend.tf           # Backend configuration
  โ”œโ”€โ”€ providers.tf         # Provider configuration
  โ”œโ”€โ”€ network.tf           # Network resources
  โ”œโ”€โ”€ compute.tf           # Compute resources
  โ”œโ”€โ”€ database.tf          # Database resources
  โ”œโ”€โ”€ terraform.tfvars     # Variable values
  โ””โ”€โ”€ modules/
      โ”œโ”€โ”€ vm/
      โ”œโ”€โ”€ aks/
      โ””โ”€โ”€ storage/

Quick Reference Card

Essential Commands

# Initialize
terraform init
terraform init -upgrade

# Plan
terraform plan
terraform plan -out=tfplan
terraform plan -var-file="prod.tfvars"

# Apply
terraform apply
terraform apply tfplan
terraform apply -auto-approve

# Destroy
terraform destroy
terraform destroy -target=azurerm_resource_group.example

# Format & Validate
terraform fmt -recursive
terraform validate

# State
terraform state list
terraform state show azurerm_resource_group.main
terraform state mv
terraform state rm

# Workspaces
terraform workspace list
terraform workspace new prod
terraform workspace select prod

# Output
terraform output
terraform output -json

Common Azure Resources

# Resource Group
resource "azurerm_resource_group" "example" {
  name     = "rg-example"
  location = "East US"
}

# Virtual Network
resource "azurerm_virtual_network" "example" {
  name                = "vnet-example"
  address_space       = ["10.0.0.0/16"]
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name
}

# Subnet
resource "azurerm_subnet" "example" {
  name                 = "subnet-example"
  resource_group_name  = azurerm_resource_group.example.name
  virtual_network_name = azurerm_virtual_network.example.name
  address_prefixes     = ["10.0.1.0/24"]
}

# Storage Account
resource "azurerm_storage_account" "example" {
  name                     = "stexample"
  resource_group_name      = azurerm_resource_group.example.name
  location                 = azurerm_resource_group.example.location
  account_tier             = "Standard"
  account_replication_type = "LRS"
}

# AKS Cluster
resource "azurerm_kubernetes_cluster" "example" {
  name                = "aks-example"
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name
  dns_prefix          = "aks-example"
  
  default_node_pool {
    name       = "default"
    node_count = 3
    vm_size    = "Standard_D2s_v3"
  }
  
  identity {
    type = "SystemAssigned"
  }
}

๐ŸŽ“ Terraform with Azure Master Class Complete
This comprehensive guide covers everything from Terraform basics to advanced Azure infrastructure patterns. Use this as your reference for building production-ready infrastructure as code on Microsoft Azure.