Terraform import, moved and removed Blocks: Refactor Without Destroying

· 2 min read · Terraform & OpenTofu Tutorials

Change what Terraform manages without touching the infrastructure #

Sooner or later you need to bring existing infrastructure under Terraform, rename a resource, move it into a module or stop managing it. All of these change the state and the code, but must not change the real infrastructure. Terraform (1.5 and later) and OpenTofu (1.6 and later) do it with configuration blocks that go through plan and code review, instead of the old imperative commands.

import blocks: adopt existing resources #

Suppose an S3 bucket was created by hand in the console:

import.tf
import {
  to = aws_s3_bucket.legacy
  id = "ditwl-legacy-bucket"
}

resource "aws_s3_bucket" "legacy" {
  bucket = "ditwl-legacy-bucket"
}

terraform plan shows that the resource will be imported, and apply adds it to the state. The id format depends on the resource and is documented at the end of each resource page in the provider documentation.

You can ask Terraform to write the resource code for you:

$ terraform plan -generate-config-out=generated.tf

Review the generated file before using it: it contains every attribute, including some computed ones that you should remove.

After the import is applied you can delete the import block. With for_each in the import block (supported in current versions) you can import many resources at once.

moved blocks: rename or relocate a resource #

Renaming aws_instance.web to aws_instance.app makes Terraform destroy one and create the other. A moved block tells it that it is the same object:

moved.tf
moved {
  from = aws_instance.web
  to   = aws_instance.app
}

It also works to move a resource into a module, or between modules:

moved-module.tf
moved {
  from = aws_vpc.main
  to   = module.network.aws_vpc.main
}

Keep the moved blocks while there may be other users of the module with the old state, then you can remove them. Modules published for others should keep them.

removed blocks: stop managing without destroying #

To remove a resource from the code without deleting the real infrastructure, use a removed block (Terraform 1.7 and OpenTofu 1.7 and later):

removed.tf
removed {
  from = aws_s3_bucket.legacy

  lifecycle {
    destroy = false
  }
}

The resource is forgotten by the state, and the bucket keeps existing. Without destroy = false, removing the block from the code means destroy.

The command line equivalents #

Before these blocks existed the same was done with commands. You may still find them in older tutorials:

$ terraform import aws_s3_bucket.legacy ditwl-legacy-bucket
$ terraform state mv aws_instance.web aws_instance.app
$ terraform state rm aws_s3_bucket.legacy

They change the state immediately and without a plan, so they are easy to get wrong and leave nothing in the code history. Prefer the blocks, especially in a team or in a CI/CD pipeline.

Checklist #

  1. Back up the state (or use a versioned backend).
  2. Write the block and run plan.
  3. Confirm the plan says import or move, with no destroy.
  4. Apply, then run plan again to confirm no changes remain.

Related: lifecycle, for_each vs count and migrating from Terraform to OpenTofu.

#Terraform #OpenTofu #AWS