What are Terraform moved blocks and how do you refactor without destroying resources?
Quick Answer
Moved blocks declare that a resource has been renamed or relocated within your configuration, allowing Terraform to update the state mapping without destroying and recreating the actual infrastructure. They replace the manual terraform state mv command with a declarative, version-controlled approach to refactoring.
Detailed Answer
Terraform moved blocks, introduced in Terraform 1.1, provide a declarative way to refactor your configuration without destroying and recreating real infrastructure. Think of it like a postal forwarding address: you tell Terraform that the resource formerly known as aws_instance.web_server now lives at module.compute.aws_instance.payments_api, and Terraform updates its internal address book (the state file) without touching the actual mail (cloud resources).
Before moved blocks, refactoring Terraform code was perilous. If you renamed a resource from aws_rds_instance.database to aws_rds_instance.payments_db, Terraform would plan to destroy the old resource and create a new one, because it tracks resources by their configuration address. The workaround was terraform state mv, an imperative command that modifies state directly. This was error-prone, not version-controlled, and required coordination: every team member had to run the state mv command in the right order before applying, or their plan would show destructive changes.
Moved blocks solve this elegantly. You add a moved block to your configuration that declares the old and new addresses. When Terraform processes the configuration, it reads the moved blocks before computing the plan, updates the state mapping in memory, and then generates a plan based on the corrected addresses. The result is a plan that shows no changes (or only the changes you intentionally made), and the state file is updated as part of the normal apply process.
Moved blocks support several refactoring patterns. Resource renaming is the simplest: changing a resource's logical name. Module restructuring lets you move resources into or out of modules, or between modules. Adding for_each to a resource that was previously a single instance requires moving from the singleton address to an indexed address. You can also use moved blocks when splitting a monolithic configuration into smaller modules.
Production gotchas are important to understand. Moved blocks are processed in order, so if you have a chain of moves (A to B, then B to C), they must be listed in sequence. You cannot move a resource across provider boundaries: moving aws_instance to google_compute_instance is not valid because the underlying APIs and schemas differ. Moved blocks should be kept in configuration temporarily during the transition period and removed after all workspaces have applied the change, because stale moved blocks referencing non-existent source addresses will generate warnings. When moving resources that have count or for_each, you must specify the exact index or key in both the from and to addresses. Finally, moved blocks do not work across state file boundaries: you cannot move a resource from one state to another using moved blocks alone; that still requires terraform state mv with the -state flags or import blocks.