Lesson 15 of 16
Section 4: Enterprise Modular Architecture, GitOps & Production Security35 min read & walkthrough

Zero-Downtime Refactoring: The Moved Block Protocol

01 / REFACTOR
Zero Downtime

Safely reorganize code architecture without triggering resource recreation.

02 / MOVED BLOCK
Native HCL

Declare address translations directly in code: 'from = ... to = ...'.

03 / AUDITABLE
Git Tracked

Move statements are committed to git and executed automatically during team CI/CD runs.

The Big Idea

In earlier versions of Terraform, if you refactored your code by wrapping a standalone aws_security_group.web into a reusable module.security.aws_security_group.web, Terraform treated this as a complete deletion and recreation.

To prevent downtime, engineers had to coordinate risky manual terraform state mv CLI commands across every team member's machine and CI/CD pipelines. One missed step caused database outages or firewall dropped connections.

Terraform 1.1 introduced the game-changing moved Block. You can now declare refactoring intentions directly inside your HCL code. When Terraform runs, it reads the moved block, automatically updates the state addresses in place, and confirms in the plan output that zero infrastructure will be destroyed.

💡

Jargon Buster: Plain-English Translations

  • Moved Block: A declarative configuration block that instructs Terraform to re-address existing resources in state without recreation.
  • Resource Re-addressing: Changing the internal identifier or module hierarchy of a resource in the state ledger.
  • Refactoring Plan: A plan output that shows only state address modifications ('has moved to') with 0 to add, 0 to change, 0 to destroy.

Refactoring Standalone Resources into Modules with Moved Blocks

# Scenario: We refactored our standalone VPC into a reusable child module.

# OLD CODE (Before Refactoring):
# resource "aws_vpc" "core" {
#   cidr_block = "10.0.0.0/16"
# }

# NEW CODE (After Refactoring):
module "network" {
  source   = "./modules/vpc"
  vpc_cidr = "10.0.0.0/16"
}

# The Magic: Declarative Moved Blocks guarantee ZERO downtime
moved {
  from = aws_vpc.core
  to   = module.network.aws_vpc.main
}

moved {
  from = aws_subnet.public_a
  to   = module.network.aws_subnet.public["us-east-1a"]
}

# Result in 'terraform plan':
# aws_vpc.core has moved to module.network.aws_vpc.main
# Plan: 0 to add, 0 to change, 0 to destroy.
⚠️

Beginner Traps & Gotchas

1. Deleting moved blocks immediately after your first apply

The Gotcha: Deleting the moved blocks before other team members or CI/CD pipelines have pulled and applied the changes.

The Fix: Keep moved blocks in your repository for at least a few release cycles so all branches and environments migrate cleanly.

2. Typos in the from or to address strings

The Gotcha: Writing 'to = module.network.aws_vpc.core' when the module resource is named 'main'.

The Fix: Always inspect the speculative plan thoroughly. If the plan says '1 to destroy, 1 to add' instead of 'has moved to', check your addresses.