Terraform Footguns¶
Mistakes that destroy infrastructure, corrupt state, or create drift you'll spend days chasing.
1. Running terraform apply without reading the plan¶
You type terraform apply and hit yes without reading the output. Terraform replaces your RDS instance because you changed the engine version. That's a full data loss unless you have backups.
Fix: Always run plan first. In CI, use terraform plan -out=plan.tfplan then terraform apply plan.tfplan. Read every destroy and replace line.
2. State file in git¶
You committed terraform.tfstate to the repo. Now your AWS access keys, database passwords, and every resource detail are in git history forever. Even if you delete the file, it's in the history.
Fix: Use remote state (S3 + DynamoDB, GCS, Terraform Cloud). Add *.tfstate* to .gitignore from day one.
3. No state locking¶
Two engineers run terraform apply at the same time. Both read the same state, both try to modify it. State gets corrupted. Resources are orphaned.
Fix: Enable state locking. For S3 backend, add a DynamoDB table. For GCS, locking is built-in.
4. terraform destroy on the wrong workspace¶
You're in the prod workspace but think you're in dev. You type terraform destroy. Everything is gone.
Fix: Always check terraform workspace show before destroy. Better: use separate state files per environment, not workspaces. Add -target safety checks.
5. Changing a resource attribute that forces replacement¶
You change ami on an EC2 instance, or engine_version on an RDS. Terraform doesn't update in-place — it destroys and recreates. Your database is gone.
Fix: Read the provider docs. Look for (forces replacement) in plan output. Use lifecycle { prevent_destroy = true } on critical resources. Use create_before_destroy when possible.
6. Not pinning provider versions¶
You run terraform init and get the latest AWS provider. It has a breaking change. Your terraform plan now wants to modify 47 resources that haven't changed.
Fix: Pin providers: version = "~> 5.0". Lock with .terraform.lock.hcl (commit this file). Update providers intentionally, not accidentally.
7. count vs for_each ordering disaster¶
You use count for a list of subnets. Someone removes the second subnet. Terraform wants to destroy subnets 2, 3, 4 and recreate them as 2, 3. This shuffles your entire infrastructure.
Fix: Use for_each with a map or set. Resources are keyed by name, not index position.
8. Circular dependencies¶
Resource A references Resource B, and B references A. Terraform can't figure out the order. You get a cryptic error that takes an hour to debug.
Fix: Use depends_on explicitly. Or restructure so the dependency is one-way. Use data sources to break cycles.
9. Importing existing resources without matching config¶
You terraform import a resource but the Terraform config doesn't match reality. Next plan shows a wall of changes. You apply it and it modifies the live resource to match your (wrong) config.
Fix: After importing, run terraform plan and adjust your config until the plan shows no changes. Only then proceed.
10. Secrets in terraform.tfvars¶
You put db_password = "supersecret" in a .tfvars file and commit it. Or you use -var on the command line and it shows up in shell history and CI logs.
Fix: Use environment variables (TF_VAR_db_password), a secrets manager, or SOPS-encrypted files. Never commit secrets to any file Terraform reads.
11. ignore_changes hiding drift¶
You add ignore_changes = [tags] to stop plan noise. Now someone manually changes a critical tag in the console and Terraform never notices. Months later you can't figure out why your cost allocation is wrong.
Fix: Use ignore_changes sparingly. Prefer fixing the root cause of drift. If you must ignore, document why in a comment.
12. Deleting a resource from config without removed block¶
You delete a resource block from your Terraform files. Terraform interprets this as "destroy that resource." Your production load balancer vanishes.
Fix: Use terraform state rm to remove from state without destroying. Or use removed { from = ... lifecycle { destroy = false } } in recent versions.