Refactoring State with moved Blocks
State ties resource addresses (like aws_instance.web) to real-world object IDs. If you rename a resource in configuration, change a module's name, or convert a single resource into a for_each-based collection, the address in configuration no longer matches the address recorded in state — and by default, Terraform interprets that as 'the old resource should be destroyed and a new one created,' even though nothing about the underlying infrastructure actually needs to change. Before Terraform 1.1, the only fix was the imperative terraform state mv command, run by hand outside of version control. The moved block, introduced in 1.1, lets you declare that mapping directly in configuration instead.
Cricket analogy: If a scorer's records list a bowler under his old team name after a mid-series trade, the system flags him as a brand-new player with zero wickets instead of recognizing his career carries over — the same false 'destroy and recreate' Terraform assumes on a rename.
How moved Blocks Work
A moved block specifies a from address and a to address. When Terraform plans, it checks whether the from address still exists in state; if so, it silently updates the state entry to the to address instead of proposing a destroy/create pair. Because this logic lives in configuration, it travels with the code through version control and gets applied automatically for every consumer of that configuration — including in CI pipelines where no human is present to run an interactive state mv command.
Cricket analogy: A transfer ledger entry saying 'credit this player's stats from his old team ID to his new team ID' quietly updates the record book the moment the trade is confirmed, rather than wiping his career stats and starting fresh — exactly what a moved block does with state.
Common Refactoring Scenarios
moved blocks handle several everyday refactors: renaming a resource label (aws_instance.web to aws_instance.app_server), moving a resource into or out of a module (aws_instance.web to module.compute.aws_instance.web), and converting a resource from a single instance to count or for_each (aws_instance.web to aws_instance.web[0]). Multiple moved blocks can be chained across successive refactors, and Terraform will follow the chain, though it's good practice to periodically clean up old entries once every consumer has applied past them.
Cricket analogy: moved blocks cover renaming a player on the scorecard (Kohli to V.Kohli), moving him between squads (Kohli to squad.rcb.Kohli), and splitting one all-rounder's stats into separate batting and bowling entries — chainable across successive seasons of restructuring, but worth pruning once settled.
moved {
from = aws_instance.web
to = aws_instance.app_server
}
resource "aws_instance" "app_server" {
ami = "ami-0abcd1234ef567890"
instance_type = "t3.medium"
}
# Moving a resource into a module
moved {
from = aws_security_group.web_sg
to = module.networking.aws_security_group.web_sg
}A moved block works like a forwarding address you file with the post office after moving house: mail (state) addressed to the old location still finds you at the new one, instead of being marked undeliverable and triggering someone to build you an entirely new house.
moved blocks only prevent a destroy/create when the resource's underlying identity truly hasn't changed. If you rename a resource and simultaneously change an immutable argument that forces replacement anyway, Terraform will still show a replace — the moved block only handles the address mapping, not argument-driven replacement.
- moved blocks declare that a resource address changed without the underlying real-world object changing.
- Without a moved block, Terraform's default assumption for a changed address is destroy the old, create a new one.
- moved blocks are checked in configuration during plan, so they work automatically in CI without an interactive command.
- Common uses: renaming a resource, moving it into or out of a module, or converting it to count/for_each addressing.
- moved blocks can be chained across multiple historical refactors and Terraform follows the chain.
- moved blocks only remap addresses — they don't suppress replacement caused by an actual immutable argument change.
Practice what you learned
1. What problem do moved blocks solve?
2. In which Terraform version were moved blocks introduced?
3. What command did teams rely on before moved blocks existed to remap state addresses?
4. Why are moved blocks advantageous in CI/CD pipelines compared to terraform state mv?
5. Will a moved block prevent replacement if the renamed resource's configuration also changes an immutable, force-new argument?
Was this page helpful?
You May Also Like
The Terraform State File
Terraform's state file is the source of truth mapping your configuration to real-world resources. Understanding its structure and risks is essential for safe, collaborative infrastructure management.
Importing Existing Infrastructure
Importing lets Terraform take ownership of resources that already exist in a cloud account, bringing them under state management without recreating them.
Terraform Modules Explained
Modules are Terraform's mechanism for packaging and reusing configuration. This topic explains what a module is, root vs. child modules, and how module calls connect inputs, resources, and outputs.
count and for_each
Compare Terraform's two meta-arguments for creating multiple copies of a resource or module, and understand why for_each is usually the safer choice.
Related Reading
Related Study Notes in DevOps
Browse all study notesNginx Study Notes
DevOps · 30 topics
DevOpsAnsible Study Notes
DevOps · 30 topics
DevOpsAdvanced Kubernetes Study Notes
Kubernetes · 30 topics
DevOpsAdvanced Bash Scripting Study Notes
Bash · 30 topics
DevOpsApache Kafka Study Notes
Kafka · 30 topics
DevOpsDocker & Kubernetes Study Notes
YAML · 40 topics