The 5 Terraform Mistakes Everyone Makes (and How to Avoid Them) š°ļø
Published on 2025-09-06 by Mathieu
The 5 Terraform Mistakes Everyone Makes (and How to Avoid Them) š°ļø
šÆ TL;DR
Terraform mistakes are more common than lightsaber duels. State files left local, hardcoded secrets, unmodularized code, ignored dependencies, and blind appliesāthese are the Sith Lords of infra chaos.
This guide exposes them all, gives you real-world fixes, and adds a bonus on version pinning (because surprise upgrades are fun only at birthday parties).
1ļøā£ Neglecting State File Management šļøā ļø
Why it Matters:
The terraform.tfstate file isnāt just a boring JSON blobāitās the single source of truth for your entire infrastructure. Lose it, corrupt it, or expose it, and youāre flying the Millennium Falcon blindfolded. Expect:
- Orphaned resources you canāt destroy
- Drift between what Terraform thinks vs. whatās real
- Security breaches if secrets leak from state
Classic Blunder:
Treating state files like casual docsākept on laptops, shared in Slack/email, or (gasp) versioned in GitHub. Cue race conditions and expensive investigations into āwho ran apply last?ā
Pro Tips:
- ā Use remote state backends (Azure Blob, AWS S3 + DynamoDB, GCP Cloud Storage, Terraform Cloud).
- š Encrypt state at rest and in transit.
- š„ Restrict access with least privilege IAM.
- š Enable state locking to prevent concurrent applies.
- š Enable versioning/soft-delete for rollback.
- š Monitor access logs (CloudTrail, Azure Monitor).
š” Jedi Tip: Treat your state file like a root password. Lose it, and the Dark Side wins.
2ļøā£ Hardcoding Values Instead of Using Variables š¤¦āāļøš·ļø
Why it Matters:
Hardcoding is a shortcut to chaos. Secrets, regions, or IDs sprinkled everywhere lead to:
- Broken portability (dev works, prod fails)
- Copyāpaste madness
- Leaked secrets in plain text
- Painful onboarding
Classic Blunder:
location = "westeurope" in ten files. db_password = "SuperSecret123!" hardcoded. It worksāuntil it doesnāt.
Pro Tips:
- ā
Use
variables.tfwith validation for env/region. - š Pass secrets via env vars or secret managers (Vault, Azure Key Vault, AWS Secrets Manager).
- š§© Standardize naming/tagging with
locals.tf. - š” Fetch IDs dynamically with
datasources. - š”ļø Run
tflintorcheckovto block hardcoding in CI/CD.
š” Jedi Tip: Every hardcoded value is future technical debt. Use variables, locals, and data sources like lightsabers to stay elegant.
3ļøā£ Not Using Terraform Modules for Reusability š§©
Why it Matters:
Terraform without modules = a copy-paste jungle. Updates turn into whack-a-mole across dozens of files. Modules bring DRY (Donāt Repeat Yourself) to infra:
- Reuse across environments
- Centralized updates
- Enforced standards
- Faster onboarding
Classic Blunder:
Ten bucket definitions, all slightly different. One without encryption. One missing lifecycle rules. Whoops.
Pro Tips:
- ā
Use vetted community modules (
terraform-aws-vpc,terraform-azurerm-vnet). - š ļø Create internal modules for VPCs, IAM, storage.
- š Pin module versions (
ref=v1.2.3) to avoid surprise breakage. - š§Ŗ Test modules in isolation before rollout.
- š§± Keep modules small and composable (network, app, db).
š” Jedi Tip: Modules are LEGOĀ® bricks. Snap them together. Copy-paste infra is glueāit breaks when you need to change.
4ļøā£ Ignoring Resource Dependencies š¦
Why it Matters:
Terraform builds a dependency graph, but bad references or forced orders lead to:
- Race conditions
- Failed applies
- Subtle bugs (dangling SGs, unusable VMs)
Classic Blunder:
Creating compute before networks are ready. Instances fail, pipelines stall, engineers rage.
Pro Tips:
- ā Reference resource attributes for implicit dependencies.
- š ļø Use
depends_ononly when Terraform canāt infer. - š Inspect plans for suspicious order/skipped steps.
- šŗļø Visualize with
terraform graph | dot -Tpng > graph.png. - š Design modules with clear input/output contracts.
š” Jedi Tip: Trust the graph, but verify. Overusing
depends_onmeans your design probably needs a rethink.
5ļøā£ Not Reviewing the PlanāTrusting terraform apply Blindly š
Why it Matters:
Terraformās plan is your crystal ball. Skipping it is like deploying DB migrations without reading the diff. Risks:
- Unintentional destroys
- Misconfigured variables
- Missed drift
- Lost trust after an āinnocentā commit nukes prod
Classic Blunder:
Piping terraform apply -auto-approve straight into CI/CD. Congratsāyouāve automated Russian roulette.
Pro Tips:
- ā
Always run & review
plan. Store plan artifacts in CI. - š Require PR reviews & mandatory checks (
fmt,validate,tflint,checkov). - š Ban default auto-approveālimit it to dev/ephemeral envs.
- š Flag ādiff noiseā (unexpected deletes/creates) with policies.
- š¢ Post plan summaries in Slack/Teams for transparency.
š” Jedi Tip: The plan shows you the future. If it looks scary, stop. The Force is literally warning you.
š„ Bonus: Version Pinning and Upgrades
Why it Matters:
Providers and modules evolve fast. Floating on latest is gamblingāsudden changes = broken infra.
Unpinned versions cause:
- Surprise breakage
- Inconsistent environments
- Drift from changed defaults
Classic Blunder:
Leaving provider unpinned:
provider "azurerm" {
features {}
}
Or GitHub modules without ref:
module "network" {
source = "git::https://github.com/my-org/network"
}
Pro Tips:
⢠ā
Pin providers with required_providers + commit .terraform.lock.hcl.
⢠š¦ Pin modules (version = "x.y.z" or ?ref=v1.2.1).
⢠𧪠Test upgrades in non-prod with plan.
⢠š Schedule upgrades regularlyādonāt wait years.
⢠š¦ Automate checks with Dependabot/Renovate.
š” Jedi Tip: Version pinning = predictability. Keep surprises for birthday parties, not prod.
āø»
š¤ Did You Know?
⢠Terraformās first release was in 2014, same as Kubernetes!
⢠terraform console helps debug values in real time.
⢠Plans can be saved (-out=tfplan) and reused safely.
⢠Remote backends support access loggingāhandy for audits.
⢠Terraform Registry has thousands of modulesādonāt reinvent the (Millennium) Falcon.
āø»
š§ Tech Glossary
⢠Infrastructure as Code (IaC): Manage infra with code.
⢠State File: JSON file tracking deployed resources.
⢠Remote State: Shared, secure backend for state.
⢠Module: Reusable Terraform config block.
⢠Variable: Parameterized input for flexibility.
⢠Resource Dependency: Ordering between resources.
⢠Tagging: Metadata for cost/ownership tracking.
⢠Plan: Preview of changes before apply.
⢠Drift: Real infra diverges from code/state.
āø»
š” Key Takeaways
⢠Treat your state file like the Crown Jewels.
⢠Use variables/modulesāavoid hardcoding.
⢠Respect dependencies; never skip reviewing the plan.
⢠Pin versions for predictable upgrades.
⢠Small habits = stable cloud.
āø»
āļø FAQ: Frequently Asked Terraform Questions
Q: Is it ever okay to keep state files local?
A: Only for experiments. For teams, always use remote backends.
Q: Can I use secrets in variables?
A: Never hardcode. Use env vars or secret managers.
Q: Whatās a good tagging standard?
A: At minimum: Environment, Owner, Project, Purpose, CostCenter. Enforce with policies.
Q: Should I always modularize?
A: Flat files are fine for POCs. For real projects, modularize from day one.
Q: Why do upgrades break things?
A: Providers/modules change. Pin versions and test upgrades in isolation first.
āø»
š Final Thoughts
Terraform is powerful, but mistakes happenāeven to experienced engineers. From state slip-ups to tagging chaos, weāve all had āwhoopsā moments that made us want to hide in a Death Star trash compactor.
The secret isnāt perfectionāitās guardrails and habits. Remote state, reusable modules, ruthless tagging, careful plan reviews, and pinned versions. These turn chaos into boring, stable infra.
Because in Terraform, boring is beautiful.
And remember: Stay nerdy, keep learning, and keep Talking Nerdy to Me! š
---
Tags: terraform, iac, cloud