Terraform is declarative infrastructure as code that compares configuration, state, and provider APIs to produce a plan. The AzureRM provider offers modeled Azure resources; the AzAPI provider can reach newer ARM resource types. Terraform state maps configuration to real objects and can contain sensitive values, so it is critical production data.
Lab: 45 minutes · Cost: Terraform has no Azure charge; its state storage and managed resources can charge · Never: commit state or plan files to Git.
Method note: This chapter deliberately teaches Terraform's stateful workflow as one tool rather than presenting Portal or Bicep as equivalent steps. Use the preceding Bicep chapter for the Azure-native declarative path and the service chapters for Portal and command-line alternatives.
Keep modules focused and versioned. Separate state by environment and blast radius. A single state for an entire enterprise creates slow plans, excessive privilege, and risky coupling.
The example constraint is a starting pattern, not a promise about the current newest version. Commit .terraform.lock.hcl, review provider upgrades, and test them.
Saved plans can contain sensitive values and become stale. Protect and delete them under your pipeline's artifact policy. Do not apply a plan built for a different commit or environment.
Use an Azure Storage backend with private access, encryption, soft delete/versioning as appropriate, restricted RBAC, and monitoring. Bootstrap it through a separate controlled process because Terraform cannot use a backend that does not yet exist.
Use workload identity in CI. Do not supply storage access keys. Azure Blob leases provide state locking behavior; investigate and resolve a lock rather than force-unlocking blindly.
Use AzAPI when Azure exposes an ARM resource/property before AzureRM models it. Pin the resource API version, validate schema, write tests, and plan migration to AzureRM if it later gains support. AzAPI is not permission to deploy unreviewed preview features.
Use import blocks or terraform import to associate existing resources after configuration is written. The first plan should show no unexpected mutation. Use moved blocks when renaming resources/modules so Terraform changes addresses without destroying the real object.
Never “fix” drift by deleting state entries until you understand who owns the resource and what the next plan will do.
sensitive = true hides normal CLI display; it does not remove the value from state. Prefer managed identity and Key Vault references. Restrict state readers. Avoid data sources that pull secret material into state. Mark outputs sensitive and publish only necessary non-secret deployment outputs.
Run terraform plan -destroy, review it, then terraform destroy only for the isolated lab. Confirm separately managed state storage remains intact. Do not destroy shared backend or networking from an application stack.