Microsoft Azure — Terraform on Azure
Terraform provider for Azure, state management, remote backends, modules, and CI/CD integration..
20+ years shipping production infrastructure and CI/CD at scale. Drawn from code that ran under real load.
- ✓Azure subscription, Terraform CLI >= 1.5, Azure CLI >= 2.40, basic knowledge of HCL syntax, familiarity with Azure resource types (Resource Groups, VNets, App Services, SQL Database), access to create service principals
Azure is Microsoft's cloud computing platform offering over 200 services. This article covers terraform on azure with production-ready configurations, best practices, and hands-on examples.
Why Terraform on Azure?
Terraform is the de facto standard for infrastructure as code (IaC) on Azure. Unlike ARM templates or Bicep, Terraform provides a cloud-agnostic workflow, state management, and a mature module ecosystem. For teams managing multi-cloud or hybrid environments, Terraform reduces cognitive overhead by using a single language (HCL) across providers. Azure's native tools are powerful but lock you into Microsoft's ecosystem. Terraform gives you portability without sacrificing depth—AzureRM provider supports virtually every Azure resource. Production teams choose Terraform for its plan/apply cycle, drift detection, and integration with CI/CD pipelines. If you're already using Terraform for AWS or GCP, adding Azure is straightforward. If you're Azure-only, Terraform still wins on flexibility and community modules.
>= for minimum and lock with a lock file.Setting Up Authentication for Azure
Terraform needs to authenticate to Azure. The recommended approach for production is service principal authentication with a client secret or certificate. Avoid using interactive login or managed identities for local development—they don't work well in CI/CD. Create a service principal with az ad sp create-for-rbac and assign it Contributor or custom role at the subscription or resource group scope. Store secrets in a vault (Azure Key Vault, HashiCorp Vault, or CI/CD secrets). For local dev, use environment variables: ARM_CLIENT_ID, ARM_CLIENT_SECRET, ARM_SUBSCRIPTION_ID, ARM_TENANT_ID. Never hardcode secrets in files. Use az login only for quick tests; it's not repeatable.
Structuring Terraform Configurations for Azure
A well-structured Terraform project is essential for maintainability. Use a modular approach: separate root modules for environments (dev, staging, prod) and reusable child modules for resources (networking, compute, databases). Each module should have clear inputs and outputs. For Azure, group resources by lifecycle and dependency. For example, a networking module creates VNet, subnets, NSGs, and route tables. A compute module depends on networking outputs. Use terraform.tfvars files per environment to override defaults. Avoid hardcoding resource names; use naming conventions with var.environment and var.project. Keep state files in a remote backend (Azure Storage) with locking enabled.
Managing State with Azure Storage Backend
State files contain sensitive information and must be stored securely. Azure Storage Account with blob containers is the recommended backend. Enable blob soft delete and versioning to recover from accidental deletion. Use a dedicated storage account per environment with network restrictions (firewall, private endpoint). Enable azurerm_backend with key per state file (e.g., prod.terraform.tfstate). Use terraform init -backend-config to inject backend settings without hardcoding. For teams, enable state locking via Azure Blob Storage lease. This prevents concurrent modifications. Never store state locally in production.
Deploying a Multi-Tier Application on Azure
Let's deploy a typical three-tier app: web, API, and database. Use Azure App Service for web and API, and Azure SQL Database. Create a resource group, App Service Plan, two App Services, and a SQL Server with database. Use azurerm_app_service_plan with S1 SKU for production. Configure connection strings as app settings. Use azurerm_sql_database with S1 tier. For networking, enable VNet integration for App Services to connect to SQL via private endpoint. This example shows a complete, runnable configuration.
@Microsoft.KeyVault(SecretUri=...).Networking: VNets, Subnets, and NSGs
Networking is critical for security and performance. Create a VNet with subnets for each tier: web, app, data, and management. Use Network Security Groups (NSGs) to restrict traffic. For production, use azurerm_network_security_group with explicit allow rules. Avoid using * as source; specify IP ranges or service tags. Use azurerm_subnet_network_security_group_association to attach NSGs. For App Services, use VNet Integration (regional) to route traffic through the VNet. For databases, use Private Endpoints to keep traffic within Azure backbone.
Using Terraform Workspaces for Environment Isolation
Workspaces allow you to manage multiple environments with the same configuration. Create workspaces for dev, staging, prod. Each workspace has its own state file. Use terraform.workspace in configurations to differentiate resources. For example, name = "app-${terraform.workspace}". Workspaces are simple but have limitations: they share the same backend and variable values. For complex environments, use separate directories or Terragrunt. Workspaces work well for small teams with similar environments. Avoid using workspaces for completely different configurations (e.g., different regions).
CI/CD Pipeline for Terraform on Azure
Automate Terraform with Azure DevOps or GitHub Actions. The pipeline should: validate formatting, run terraform init, terraform validate, terraform plan, and manual approval before terraform apply. Store backend config and secrets as pipeline variables. Use terraform plan -out=tfplan to capture the plan, then terraform apply tfplan for consistency. For Azure DevOps, use the Terraform task or bash scripts. For GitHub Actions, use the hashicorp/setup-terraform action. Always run terraform fmt -check to enforce style. Fail the pipeline on validation errors.
Handling Secrets and Sensitive Data
Never store secrets in Terraform state or configuration files. Use Azure Key Vault to store secrets and reference them in Terraform with data.azurerm_key_vault_secret. For provider authentication, use environment variables or managed identities. For application secrets (e.g., DB passwords), use Key Vault references in App Settings. Terraform can create Key Vault and secrets, but avoid putting secret values in .tf files. Use sensitive = true in outputs to prevent state exposure. For CI/CD, use pipeline secret variables.
sensitive, the value may appear in logs. Use terraform output -json carefully..gitignore and pre-commit hooks to block secrets.Testing Terraform Configurations with Terratest
Testing infrastructure code prevents regressions. Use Terratest (Go library) to write integration tests that deploy real resources and verify behavior. Write tests for: resource creation, network connectivity, and idempotency. Run tests in isolated environments (e.g., ephemeral resource groups). Terratest handles setup and teardown. Example: test that an App Service returns HTTP 200. This catches misconfigurations before production. Combine with terraform plan checks in CI. Testing is not common but is a hallmark of mature teams.
Drift Detection and Remediation
Azure resources can be modified outside Terraform (manual changes, auto-scaling, etc.). This creates drift. Use terraform plan regularly to detect drift. Automate drift detection with scheduled CI/CD jobs. For critical resources, use terraform apply to remediate, but be careful—it may revert desired changes. Use terraform refresh to update state without changes. For Azure, enable Azure Policy to enforce compliance and alert on drift. Consider using azurerm_resource_group_template_deployment for resources that must stay in sync. Drift is inevitable; plan for it.
terraform plan is essential to maintain desired state.Advanced: Using Azure Policy with Terraform
Azure Policy enforces organizational rules. Terraform can create policy assignments and definitions. Use azurerm_policy_definition and azurerm_policy_assignment to apply policies like 'allowed locations' or 'require encryption'. This ensures resources created by Terraform (or manually) comply. Combine with azurerm_policy_set_definition for initiatives. Policies can also auto-remediate non-compliant resources. Use azurerm_management_group to scope policies. This is advanced but critical for large organizations.
policy_rule parameter to keep HCL clean.| File | Command / Code | Purpose |
|---|---|---|
| main.tf | terraform { | Why Terraform on Azure? |
| auth.sh | az ad sp create-for-rbac --name "terraform-sp" --role Contributor --scopes /subs... | Setting Up Authentication for Azure |
| modules | resource "azurerm_resource_group" "rg" { | Structuring Terraform Configurations for Azure |
| backend.tf | terraform { | Managing State with Azure Storage Backend |
| app.tf | resource "azurerm_resource_group" "rg" { | Deploying a Multi-Tier Application on Azure |
| network.tf | resource "azurerm_virtual_network" "vnet" { | Networking |
| workspaces.sh | terraform workspace new dev | Using Terraform Workspaces for Environment Isolation |
| azure-pipelines.yml | trigger: | CI/CD Pipeline for Terraform on Azure |
| secrets.tf | data "azurerm_key_vault" "kv" { | Handling Secrets and Sensitive Data |
| terraform_test.go | "testing" | Testing Terraform Configurations with Terratest |
| drift.sh | terraform init -backend-config="..." | Drift Detection and Remediation |
| policy.tf | resource "azurerm_policy_definition" "allowed_locations" { | Advanced |
Key takeaways
terraform plan in CI to detect and remediate drift, ensuring infrastructure stays compliant.Frequently Asked Questions
20+ years shipping production infrastructure and CI/CD at scale. Drawn from code that ran under real load.
That's Azure. Mark it forged?
4 min read · try the examples if you haven't