6.3.2. Refactoring with moved and removed Blocks
💡 First Principle: The moved and removed blocks let you change a resource's address or management status as a deliberate state edit recorded in code — so refactors that used to require risky manual state mv/state rm commands are now reviewable, repeatable configuration.
The moved block tells Terraform that a resource's address changed (you renamed it, moved it into a module, or restructured count/for_each) so it should update state in place instead of destroying and recreating:
moved {
from = aws_instance.web
to = aws_instance.web_server
}
The removed block declares that a resource (or module) has been removed from configuration but should not be destroyed — Terraform drops it from state while leaving the real object intact:
removed {
from = aws_instance.legacy
lifecycle {
destroy = false # remove from state only; keep the real resource
}
}
| Block | Purpose | Effect on real infrastructure |
|---|---|---|
moved | Rename / relocate a resource address | None — state-only address change |
removed (with destroy = false) | Stop managing a resource | None — left intact, dropped from state |
Both replace older error-prone manual workflows (terraform state mv, terraform state rm plus lifecycle tricks) with declarative blocks that live in version control and are applied through the normal plan/apply cycle.
⚠️ Exam Trap: Neither block changes infrastructure when used as intended: moved is a state-only rename (no destroy/recreate), and removed with destroy = false orphans the real resource from Terraform's management without deleting it. The trap is assuming "moved" or "removed" implies a real-world action — they're about Terraform's record.
Reflection Question: You renamed aws_instance.app to aws_instance.api. Without a moved block, what does Terraform plan — and how does the moved block change that outcome?