Hedronite · Cert Lesson · Terraform Associate 003 · Sun 2026-10-11 · BACKFILL

Terraform Associate moved blocks for renames and module refactors without replacement

Change the address and Terraform replaces it, unless you say where it went.

Lesson Class: Cert (T7 · state and refactoring)
Referent: moved blocks · terraform state mv
Not a repeat: not 10-08 lock file · 10-05 refresh-only
Paired Ops: S3 Block Public Access
Address Is Identity
Old address, new address, written down.
Declare the Move
moved goes through review.
Read the Plan Line
has moved to, zero destroys.
Two to add, two to destroy, for a rename.

Terraform tracks resources by address. Change the address and it plans a destroy and a create, unless you tell it where the old one went.

§I. Frame

State maps each address, such as terraform_data.web, to a real object. Rename the block or move it into a module and the old address disappears from config while the new one has no state. Terraform reads that as "destroy one, create one." For a database or the S3 bucket in the paired Ops lesson, that is an outage.

Brikman covers the two fixes: the imperative terraform state mv, and since Terraform 1.1 the declarative moved block.

§II. Three techniques

Address Is Identity (named technique). Before any refactor, write down the old address and the new one. Both forms count: terraform_data.web to terraform_data.frontend (rename), and terraform_data.cache to module.site.terraform_data.cache (into a module).

Declare the Move (named technique). A moved block lives in config, goes through review, and shows up in plan:

moved {
  from = terraform_data.web
  to   = terraform_data.frontend
}

moved {
  from = terraform_data.cache
  to   = module.site.terraform_data.cache
}

terraform state mv does the same edit directly on state, outside review, one command per address, and every other workspace has to repeat it.

Read the Plan Line (named technique). The plan says has moved to for a clean move and Plan: 0 to add, 0 to change, 0 to destroy. Anything else means the move did not match.

§III. Run it

Start: two resources applied from before/main.tf.

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
terraform_data.cache
terraform_data.web

Swap in after/ (rename plus a new module "site"), without the moved blocks:

  # terraform_data.cache will be destroyed
  # terraform_data.frontend will be created
  # terraform_data.web will be destroyed
  # module.site.terraform_data.cache will be created
Plan: 2 to add, 0 to change, 2 to destroy.

Same config with the two moved blocks (real plan, trimmed):

  # terraform_data.web has moved to terraform_data.frontend
  # terraform_data.cache has moved to module.site.terraform_data.cache
Plan: 0 to add, 0 to change, 0 to destroy.

Apply, then terraform state list:

terraform_data.frontend
module.site.terraform_data.cache

The ids in the plan (b06e3194-..., cc241cc7-...) are the same objects, now at new addresses.

§IV. Exam edges

§V. Drills

  1. You rename aws_s3_bucket.logs to aws_s3_bucket.audit_logs and plan. What do you expect without a moved block? (One destroy, one create.)
  2. Which approach is visible in code review? (moved block.)
  3. The plan shows has moved to and 1 to change. Is the move broken? (No; the move matched and an attribute also changed.)
  4. Where does from point when moving a resource into module.site? (The old root address; to is module.site.<type>.<name>.)

Close

Address Is Identity, Declare the Move, Read the Plan Line. Write the moved block, plan, and look for zero destroys before you apply.

Related