Skip to main content
RunBook Academy

TerraformX · State Operations: Read, Move, Remove, ImportRefactoring

moved Blocks: Refactoring Without Recreation

Advanced⏱ ~14 min🧪 Lab requiredbashterraformgit

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

Not yet marked complete on this device.

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:

  1. Apply the current configuration.
  2. Capture the state.
  3. Rename the resource in the configuration.
  4. Add a moved block.
  5. Run terraform plan.
  6. Verify the plan is empty.
  7. Run terraform apply.
  8. Verify the new address is in the state.
  9. Verify the real-world resources are unchanged.
  10. Remove the moved block.

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

  1. Q1. What is the role of state list?

  2. Q2. What is the role of state mv?

  3. Q3. state rm destroys the real world.

  4. Q4. What is the role of state replace-provider?

  5. Q5. Which state operations mutate the state? (Select all that apply.)

  6. Q6. What is the role of the moved block?

  7. 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.