State CLI and Safe Refactoring

Scenario: A resource block is renamed purely for readability - no provider-side setting changes at all - and the very next plan proposes destroying the existing real object and creating a brand-new, identical replacement in its place.

You already saw in Module 4 why this happens: a resource's Terraform address is partly derived from its local label, so renaming the label changes the address even though the underlying real-world object is untouched. Terraform's state subcommands let you inspect and, in exceptional cases, directly manipulate the address-to-object mapping: state list shows every managed address, state show <address> prints one resource's full recorded attributes, state mv moves an object from one address to another without touching the real infrastructure, state rm tells Terraform to forget about an object (again, without touching the real infrastructure), and state pull/push download or upload the raw state file for advanced, exceptional operations.

For the common case - a deliberate rename or refactor - prefer the declarative moved block over running state mv by hand. A moved block lives right in your version-controlled .tf files, so it documents the migration intent for every future reader and, crucially, applies automatically for every collaborator and CI run the next time they plan - nobody has to remember to run a separate manual command. Reserve state mv for genuinely one-time, exceptional migrations, and always back up state first (state pull > backup.json) before running it. And note precisely what state rm does: it makes Terraform forget an object exists, without deleting the real object at all - the two are very different operations that are easy to confuse under pressure.

Analogy: Renaming a contact's saved label in your phone from "Bob" to "Bob (Accounting)" does not create a new person - the phone number, the actual person, stays exactly the same underneath. A moved block is exactly this kind of rename: it updates the address book entry while explicitly preserving the underlying identity, so Terraform never mistakes the rename for "the old contact is gone, here's a stranger."

A worked configuration

moved {
  from = fakecloud_server.web
  to   = fakecloud_server.api
}

# Exceptional equivalent:
# terraform state mv fakecloud_server.web fakecloud_server.api

A well-executed refactor should produce a near-empty plan immediately after the address moves take effect - if you renamed ten resources and the resulting plan still shows real infrastructure changes, something in the refactor is incomplete. Test any nontrivial state migration against a copy of state or in a non-production environment first, coordinate the rollout so every collaborator's local configuration updates together, and never let an old, pre-migration version of the configuration run again once a migration has gone out - it will disagree with the now-migrated state about what the correct addresses even are.

Common mistakes in production

Goal: Treat the plan as a change contract: understand every create, update, replacement, and destroy before approving it. Reinforce this lesson in the tf-moved-refactor Terraform lab. Open /labs/terraform and choose slug tf-moved-refactor; the lab runs real Terraform against the offline FakeCloud provider.