Infrastructure as Code
Infrastructure changes are code changes. Apply the same review, testing, and rollback discipline.
Module Design
- Keep modules small and single-purpose. A module for "VPC" should not also manage IAM roles.
- Accept all environment-specific values as input variables with types. No hardcoded region, account ID, or CIDR in a module body.
- Output only what callers need — don't expose internal resource IDs unless they're consumed upstream.
- Name resources with
${var.env}-${var.app}-${purpose}so resources are identifiable in cloud consoles without logging in to Terraform.
State Management
- Use remote state (S3 + DynamoDB lock, GCS, Terraform Cloud). Local
terraform.tfstatein a shared repo causes conflicts. - Use workspaces or separate state files per environment — never share state between prod and staging.
- Never edit state manually (
terraform state mv/rm) without a pairedterraform planshowing the effect. - Enable state versioning and set a lifecycle policy to retain 30+ versions.
Plan Before Apply
- Always run
terraform plan -out=tfplanand review the output beforeapply. Check for unexpected destroys. - A
-/+line (destroy-then-create) on a load balancer, database, or DNS record is a production incident waiting to happen — understand why before proceeding. - Use
terraform planin CI on every PR targeting infrastructure. Block merge on errors or diffs that weren't expected. - For stateful resources (RDS, ElasticSearch), set
lifecycle { prevent_destroy = true }and require explicit override to destroy.
Secrets and Sensitive Values
- Never store secrets in
.tffiles orterraform.tfvarscommitted to version control. - Use
sensitive = trueon output and variable blocks that carry secrets — Terraform will redact them in plan output. - Source secret values from a secrets manager (AWS SSM Parameter Store, HashiCorp Vault, GCP Secret Manager) via data sources at apply time.
Drift and Day-2
- Run
terraform planin CI on a schedule (daily) to detect drift from manual console changes. - Tag every resource with
managed-by = "terraform",env,team, andcost-center. - Use
terraform validateandtflintin pre-commit hooks to catch syntax and provider-specific errors before push.
Checklist
- Remote state configured with locking.
-
terraform planreviewed before every apply, especially for destroys. - No secrets in
.tffiles or committedtfvars. - Stateful resources have
prevent_destroy = true. - All resources tagged with env, team, and managed-by.
- CI runs
terraform planon every infrastructure PR.