Refactoring Terraform Safely with moved and removed Blocks

A
Author
12 Jun, 2026 1 min read 1.05K views

Terraform identifies resources by their address, like aws_s3_bucket.assets. Rename it in code and Terraform plans to destroy the old one and create a new one, which for a database is a disaster. moved and removed blocks tell Terraform what you really meant.

Renaming a resource

resource "aws_s3_bucket" "static_assets" {   # was "assets"
  bucket = "acme-shop-assets-prod"
}

moved {
  from = aws_s3_bucket.assets
  to   = aws_s3_bucket.static_assets
}

The plan now shows the bucket "has moved", with no destroy or create.

Moving into a module

module "storage" {
  source = "./modules/storage"
}

moved {
  from = aws_s3_bucket.static_assets
  to   = module.storage.aws_s3_bucket.assets
}

Switching to count or for_each

moved {
  from = aws_instance.web
  to   = aws_instance.web["primary"]
}

Stop managing without deleting

removed {
  from = aws_s3_bucket.legacy_reports

  lifecycle {
    destroy = false     # forget it in state, leave the bucket alone
  }
}

Useful when handing a resource over to another team's configuration.

Why blocks beat state commands

  • They're code: reviewed, versioned and applied the same way in every environment.
  • Module authors can ship moved blocks so every user of the module upgrades safely.
  • terraform state mv is a one-off manual change, easy to forget in one environment.

Extra safety for critical resources

resource "aws_db_instance" "main" {
  # ...
  lifecycle {
    prevent_destroy = true   # plan fails instead of deleting the database
  }
}
Was this article helpful?

Comments

No comments yet. Have a question or a tip? Be the first to share it.

Leave a comment

Comments are reviewed before they appear.