DevOps Peach

Advanced Terraform · State and Teamwork · Lesson 3 of 4

Refactoring: moved, removed and state Commands

Rename or move resources without destroying them using moved blocks or terraform state mv, and stop managing something without deleting it using removed blocks or terraform state rm.

Practice interactively →

Code changes all the time: a resource gets a better name, moves into a module, or switches from count to for_each. To Terraform a new address looks like a new resource, so plan wants to destroy the old object and create a new one. Refactoring tools tell Terraform "same object, new address".

flowchart LR
  OLD["docker_container.web"] -->|"moved block"| NEW["module.app.docker_container.this"]
  C0["docker_volume.data[0]"] -->|"moved"| CK["docker_volume.data["orders"]"]
  L["docker_container.legacy"] -->|"removed, destroy = false"| U["Unmanaged, still running"]
  %% step OLD: Without help, renaming an address means destroy the old object and create a new one.
  %% step NEW: A moved block records the rename in code, so every teammate's plan shows a move, not a replace.
  %% step C0: Moving from count to for_each needs one moved block per instance, index to key.
  %% step L: A removed block with destroy = false makes Terraform forget the object without deleting it.
  %% step U: The object keeps running; another configuration can import it later.

moved blocks (preferred)

moved {
  from = docker_container.web
  to   = module.app.docker_container.this
}

moved {
  from = docker_volume.data[0]
  to   = docker_volume.data["orders"]
}

Plan then shows # docker_container.web has moved to module.app.docker_container.this and 0 to destroy. Moved blocks are code: they are reviewed, they work for every workspace and every teammate, and module authors can ship them so callers upgrade safely. Keep them for a release or two, then delete them.

removed blocks

removed {
  from = docker_container.legacy
  lifecycle {
    destroy = false          # forget it, keep the real container
  }
}

Use this when another team takes over a resource or you hand it back to manual management. With destroy = true (the default) the block documents an intentional deletion.

The CLI equivalents

Command Effect
terraform state mv 'A' 'B' Change an address in state immediately
terraform state rm 'A' Forget an object
terraform state mv -dry-run 'A' 'B' Show what would move

CLI commands change the state right now, outside review, and only in the state you are pointed at (one workspace). Prefer moved/removed blocks in shared code; use the CLI for one-off repairs, with a state pull backup first.

A safe refactor, step by step

  1. Run terraform plan on the old code: it must be clean.
  2. Change the code and add moved blocks.
  3. terraform plan: expect only "has moved" lines, no destroy.
  4. Apply, merge, and remove the moved blocks in a later release.

Traps: you cannot move an object between two different resource types (docker_container to docker_service); and moving resources between two separate state files needs terraform state mv -state-out or import in the target plus removed in the source.

Practice interactively →