TerraformX · State Operations: Read, Move, Remove, ImportRefactoring
moved Blocks: Refactoring Without Recreation
What you'll learn
- Use moved blocks to rename resources
- Move resources between modules without recreation
- Verify the refactor with a plan that is empty
- Recognise when moved blocks are the right tool
Prerequisites
Verified against Terraform CLI 1.9.x · OpenTofu 1.7.x · HCL 2.0 · bpg/proxmox provider 0.66+ · hashicorp/local provider 2.5+ · hashicorp/null provider 3.2+ · hashicorp/random provider 3.6+ · hashicorp/http provider 3.4+ · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · 2026-08-12
A moved block is a declarative resource-address migration. It
tells Terraform that the resource at the from address has
been moved to the to address. The state is updated; the
plan is empty.
This is the under-used feature that allows refactoring without recreating resources. The lesson teaches the pattern.
The basic pattern
A moved block lives inside a module (or root configuration):
moved {
from = aws_instance.web
to = aws_instance.app_server
}
The block says: “the resource that was at aws_instance.web
is now at aws_instance.app_server”. The state is updated to
reflect the new address. The plan is empty.
The use case
A common refactor is renaming a resource:
# Before
resource "aws_instance" "web" {
ami = "ami-0e1bed4f"
}
# After
resource "aws_instance" "app_server" {
ami = "ami-0e1bed4f"
}
Without a moved block, the plan would propose to:
- aws_instance.web will be destroyed
+ aws_instance.app_server will be created
The aws_instance.web is destroyed; the aws_instance.app_server is created. The real-world resource is destroyed and recreated.
With a moved block:
moved {
from = aws_instance.web
to = aws_instance.app_server
}
resource "aws_instance" "app_server" {
ami = "ami-0e1bed4f"
}
The plan is empty:
No changes. Your infrastructure matches the configuration.
The state is updated to use the new address. The real-world resource is unchanged.
Moving resources between modules
A moved block can also move resources between modules:
# Before
module "network" {
source = "./modules/network"
}
# In the network module
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}
# After
module "network" {
source = "./modules/network"
}
module "vpc" {
source = "./modules/vpc"
}
# In the vpc module
moved {
from = module.network.aws_vpc.main
to = module.vpc.aws_vpc.main
}
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}
The state is updated to move the resource from the old module to the new module. The plan is empty.
The moved block is one-shot
After the refactor, the moved block is no longer needed. The
state has the new address; the configuration has the new
resource. The moved block can be removed.
If the moved block is left in the configuration, the plan
is still empty. The moved block is idempotent.
What moved does NOT do
A moved block does not:
- Rename the real-world resource.
- Rename the state path.
- Modify the dependency graph.
A moved block only updates the state. The state is the
belief about what exists. The real-world resource is
unchanged.
The dangerous pattern
A moved block can be wrong. The from and to addresses
must refer to the same real-world resource. A wrong moved
block binds the new address to the wrong resource.
# Wrong: the moved block binds app_server to the wrong instance
moved {
from = aws_instance.web
to = aws_instance.app_server
}
If aws_instance.web and aws_instance.app_server are
different real-world resources, the moved block is wrong.
The state will bind app_server to webs real-world
resource. The next apply may destroy the real-world
resource.
The fix is to verify with terraform plan after the
refactor:
No changes. Your infrastructure matches the configuration.
If the plan is empty, the moved block is correct. If the
plan is non-empty, the moved block is wrong.
The production test
The moved block is the production control for refactoring.
The test is:
- Apply the current configuration.
- Capture the state.
- Rename the resource in the configuration.
- Add a
movedblock. - Run
terraform plan. - Verify the plan is empty.
- Run
terraform apply. - Verify the new address is in the state.
- Verify the real-world resources are unchanged.
- Remove the
movedblock.
If any step fails, the moved block is wrong. Revert.
What comes next
The next lesson is refactoring safely — the broader patterns for evolving a Terraform configuration without destroying infrastructure.
Verification
Knowledge check · 7 questions
Q1. What is the role of state list?
Q2. What is the role of state mv?
Q3. state rm destroys the real world.
Q4. What is the role of state replace-provider?
Q5. Which state operations mutate the state? (Select all that apply.)
Q6. What is the role of the moved block?
Q7. A team renames a resource in the configuration. The plan proposes to destroy the old and create the new. What is the fix?
Passing score: 75%. Answers are checked in this browser.