Lesson 0002 · Understand Terraform

State, drift & refresh

One idea · ~12 min. Why Terraform needs a memory — and the drift behaviour that surprises almost everyone.
Why this next? You've got the three-picture model. The middle picture — the state file — is where most real-world Terraform surprises live. This lesson is deliberately gotcha-heavy, since lesson 0001 was easy for you.

First, the question that unlocks everything: why does state exist at all? Couldn't Terraform just look at AWS and figure it out?

No — and HashiCorp actually tried.

Early Terraform prototypes attempted exactly that, using cloud tags to mark "this resource is mine." It failed: not all resources support tags, and not all providers have them. So the config-to-real-world mapping is explicit, in state.

Three more reasons keep it there: state remembers dependencies (so it knows the destroy order even after you delete the blocks from config), it acts as a performance cache (refreshing every resource on every plan is too slow at scale — that's why -refresh=false exists), and it's the sync point for teams (one shared memory, locked during runs).

The drift rule: config wins. Refresh folds reality into state — but the diff is still Desired-vs-State. So after someone hand-edits AWS, a plain terraform apply proposes to undo their change. Drift is never silently "accepted." To accept it, you must update state (apply -refresh-only) and make the config agree.

🗑️ Destroy

Delete the resource block → next apply deletes the real resource. The block is the claim of ownership.

🧠 Forget

terraform state rm (or a removed block, TF ≥1.7) → Terraform stops managing it, real resource lives on. Inverse: import.

Now the hard part — you call it

Six scenarios, each a real-world trap. Reason from the three pictures plus the config-wins rule.

✅ Prove it on your own machine (optional, 5 min)

  1. Apply any small config, then open terraform.tfstate — find the mapping (real ID), the cached attributes, and the dependencies array.
  2. Simulate drift: change something outside Terraform (console edit, or hand-edit a terraform_data input), then run terraform plan -refresh-only — drift report, no infra changes proposed.
  3. Run plain terraform plan — watch it propose to revert the drift.
  4. Try terraform state rm on a resource, then plan — it wants to create a duplicate. Re-adopt it with terraform import (or an import block).
💬 Good follow-ups for this lesson: "What exactly does the dependencies array do during destroy?" · "When would I use a removed block vs state rm?" · "How does remote state locking actually work?" (that's where we're headed next).