Refactor Terraform Without Destruction: A Practical Guide to `moved` Blocks
Renaming a Terraform resource usually triggers a destroy and create. `moved` blocks let you refactor addresses without replacing infrastructure—if you know their limits.
30 Jul 2026, 15:13 UTC

The rename that triggers a destroy
You rename a resource in Terraform—maybe local_file.app_config becomes local_file.settings—and the next plan shows a destroy and create. Nothing changed in the real world. The file still exists, the bucket is still there. Terraform just lost track of the identity because the state address no longer matches the configuration address.
Why Terraform replaces a renamed resource
Terraform identifies every managed object by its state address, such as local_file.app_config. When you rename the resource label in configuration, the address changes. Terraform compares configuration to state, sees an object in state that is no longer declared, and a declared object that has no state. The default plan is to delete the old and create the new. For resources with immutable arguments, this can be catastrophic.
What moved blocks do
A moved block declares that an old address and a new address are the same object. It is a configuration-only refactoring tool: it tells plan and apply to reconcile state instead of replacing infrastructure.
moved {
from = local_file.app_config
to = local_file.settings
}
This works for resources, modules, and count or for_each instance keys. Moves can also be chained for staged refactors—for example, moving a resource into a module and then renaming it.
Worked example: renaming a local file
Start with a resource that writes a configuration file:
resource "local_file" "app_config" {
content = "debug=false"
filename = "app.conf"
}
After applying, Terraform tracks this as local_file.app_config. Now rename it to local_file.settings and add a moved block:
resource "local_file" "settings" {
content = "debug=false"
filename = "app.conf"
}
moved {
from = local_file.app_config
to = local_file.settings
}
Run terraform plan in your working directory. Expect no destroy/create for the file. Run terraform apply with the credentials your backend and providers require. Terraform updates state to track local_file.settings. Verify with terraform state list | grep local_file.
Limitations and cleanup
moved blocks cannot prevent replacement caused by provider-visible changes such as immutable argument updates. They only preserve identity. If your refactor also changes an argument that forces replacement—like altering a filename that the provider treats as immutable—the plan will still show a replacement.
Another limitation: moved blocks are not persisted in state. Leaving stale blocks after addresses are reconciled can make future plans ambiguous if the old address is reused. Clean them up once state and configuration agree.
Behavior is version- and provider-sensitive. Confirm support in your Terraform version and test with the exact provider versions before applying to production. If the old object was already destroyed or state was manually edited, a moved block may not reproduce the intended plan; validate in a scratch workspace first. Do not use moved as a substitute for backups or state locking.
Actionable workflow
- Run
terraform versionlocally to confirmmovedblock support (introduced in Terraform 1.1). - Add the
movedblock to your configuration. - Run
terraform planand inspect the output. You should see no destroy/create for the moved object. - Run
terraform apply. - Run
terraform state listto confirm the new address is tracked. - Run
terraform planagain to confirm there are no pending changes. - Remove the
movedblock in a later commit once state and configuration agree.
For shared infrastructure, repeat the sequence in an isolated remote workspace before touching production state.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.