100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
HCL

Common Terraform Pitfalls

A field guide to the mistakes Terraform users make most often — from state file mishandling to accidental destroys — and how to avoid each one.

Interview PrepIntermediate10 min readJul 9, 2026
Analogies

Common Terraform Pitfalls

Most Terraform incidents are not caused by exotic edge cases — they're caused by a small, recurring set of mistakes that every team eventually makes once, usually the hard way. Knowing this list in advance is one of the highest-leverage things you can do before working on production infrastructure, because most of these pitfalls are fully preventable with a small process or configuration change, but nearly invisible until you've been burned by them.

🏏

Cricket analogy: Most run-outs happen from the same handful of causes — a misjudged single, a fielder's direct hit, ball watching — not freak accidents; teams that drill those specific scenarios in nets avoid most of them before they cost a wicket in a match.

State File Mistakes

Using local state on a team project is the single most common beginner pitfall — it works fine solo, then someone else runs apply from their own laptop with a stale copy of state and either corrupts it or creates duplicate resources Terraform no longer recognizes. A closely related mistake is manually editing the state file with a text editor instead of using terraform state commands; state has non-obvious relationships (resource dependencies, provider metadata) that manual edits routinely break in ways that only surface on the next apply.

🏏

Cricket analogy: Passing a single scorebook between two scorers who each update it from memory instead of syncing live is how a team ends up with two different official totals — exactly like two engineers applying from stale local state and corrupting the record.

bash
# Wrong: hand-editing state
$ vim terraform.tfstate   # DON'T

# Right: use state subcommands
$ terraform state list
$ terraform state mv aws_instance.web aws_instance.web_primary
$ terraform state rm aws_instance.decommissioned

Blindly Approving Plans

As modules and variable interpolation grow more complex, it becomes tempting to skim a long plan output and type 'yes' out of habit. This is how accidental resource replacements slip through — a seemingly innocuous change to a tag or an ami_id can force a resource replacement (destroy + create) instead of an in-place update, and if that resource is a stateful database without deletion protection, the data is gone. Reading the plan's action symbols (+ create, -/+ destroy and recreate, ~ update in place) carefully every single time is non-negotiable discipline, not paranoia.

🏏

Cricket analogy: A batsman who stops watching the bowler's wrist and just reacts on habit will misjudge a slower ball as a yorker and get bowled — the same as an engineer who skims a Terraform plan out of habit and misses a destructive replacement.

Overusing count Instead of for_each

Using count with a list where items can be reordered or removed from the middle is a frequent source of unwanted diffs: because count addresses resources by numeric index, removing item 2 from a 5-item list shifts every subsequent resource's index, which Terraform interprets as needing to destroy and recreate several unrelated resources. for_each, addressing by a stable key, avoids this entirely. The fix is usually straightforward once diagnosed, but the destructive plan it produces is alarming to see the first time.

🏏

Cricket analogy: A team that numbers fielding positions 1 through 5 by slot instead of by player name finds that pulling one fielder from the middle reshuffles everyone else's assigned number — exactly like count reindexing resources when a list item is removed.

A particularly costly variant: using count = length(var.subnets) where var.subnets is fetched dynamically (e.g., from a data source that can change order between runs). Even without anyone editing the list, non-deterministic ordering from the upstream source can trigger unexpected resource churn on every apply.

Ignoring Provider Version Drift

Not pinning provider versions means a routine terraform init months later can silently pull a new major provider version with breaking schema changes, producing plans full of unexplained diffs or outright errors. Pinning with a version constraint (and committing a .terraform.lock.hcl file) ensures every team member and CI run resolves to the exact same provider version until someone deliberately upgrades.

🏏

Cricket analogy: Not specifying which edition of the playing conditions applies to a series means a routine rule refresh mid-tournament can silently change DRS thresholds, producing confusing umpiring decisions nobody agreed to — the same risk as unpinned provider versions.

Most Terraform 'mystery' bugs trace back to one of three root causes: stale/uncommitted lock files, someone bypassing the standard workflow with a manual console change, or a resource attribute that forces replacement without the team realizing it. Checking these three first will resolve the majority of confusing incidents.

  • Never use local state for team projects; always configure a remote backend with locking from day one.
  • Never hand-edit the state file directly — use terraform state mv/rm/list commands instead.
  • Always read the plan's action symbols carefully; don't approve applies out of habit.
  • Prefer for_each over count for any collection where items might be reordered or removed from the middle.
  • Pin provider versions and commit the .terraform.lock.hcl file to avoid silent breaking upgrades.
  • Most confusing Terraform incidents trace back to manual console drift, stale lock files, or unnoticed forced replacements.

Practice what you learned

Was this page helpful?

Topics covered

#HCL#TerraformInfrastructureAsCodeStudyNotes#DevOps#CommonTerraformPitfalls#Common#Terraform#Pitfalls#State#StudyNotes#SkillVeris