Code changes all the time: a resource gets a better name, moves into a module, or switches from count to for_each. To Terraform a new address looks like a new resource, so plan wants to destroy the old object and create a new one. Refactoring tools tell Terraform "same object, new address".
flowchart LR OLD["docker_container.web"] -->|"moved block"| NEW["module.app.docker_container.this"] C0["docker_volume.data[0]"] -->|"moved"| CK["docker_volume.data["orders"]"] L["docker_container.legacy"] -->|"removed, destroy = false"| U["Unmanaged, still running"] %% step OLD: Without help, renaming an address means destroy the old object and create a new one. %% step NEW: A moved block records the rename in code, so every teammate's plan shows a move, not a replace. %% step C0: Moving from count to for_each needs one moved block per instance, index to key. %% step L: A removed block with destroy = false makes Terraform forget the object without deleting it. %% step U: The object keeps running; another configuration can import it later.
moved blocks (preferred)
moved {
from = docker_container.web
to = module.app.docker_container.this
}
moved {
from = docker_volume.data[0]
to = docker_volume.data["orders"]
}
Plan then shows # docker_container.web has moved to module.app.docker_container.this and 0 to destroy. Moved blocks are code: they are reviewed, they work for every workspace and every teammate, and module authors can ship them so callers upgrade safely. Keep them for a release or two, then delete them.
removed blocks
removed {
from = docker_container.legacy
lifecycle {
destroy = false # forget it, keep the real container
}
}
Use this when another team takes over a resource or you hand it back to manual management. With destroy = true (the default) the block documents an intentional deletion.
The CLI equivalents
| Command | Effect |
|---|---|
terraform state mv 'A' 'B' |
Change an address in state immediately |
terraform state rm 'A' |
Forget an object |
terraform state mv -dry-run 'A' 'B' |
Show what would move |
CLI commands change the state right now, outside review, and only in the state you are pointed at (one workspace). Prefer moved/removed blocks in shared code; use the CLI for one-off repairs, with a state pull backup first.
A safe refactor, step by step
- Run
terraform planon the old code: it must be clean. - Change the code and add
movedblocks. terraform plan: expect only "has moved" lines, no destroy.- Apply, merge, and remove the moved blocks in a later release.
Traps: you cannot move an object between two different resource types (docker_container to docker_service); and moving resources between two separate state files needs terraform state mv -state-out or import in the target plus removed in the source.