Refactoring: import, moved & state ops

Adopt and reshape without destroying.

Advanced14 min · lesson 11 of 15

Two hard problems come up constantly in real Terraform: adopting infrastructure that already exists (created by hand or another tool) without recreating it, and reshaping your code (renaming, splitting modules) without Terraform seeing the new addresses as destroy-and-recreate. Both are solved by manipulating the mapping between code and state — import to bind existing reality into state, and moved to re-map addresses safely.

import: adopt existing resources

import binds a real resource to a config address in state, so Terraform starts managing it instead of trying to create a duplicate. Modern Terraform uses an import block (declarative, planned, reviewable) rather than only the older terraform import CLI command. You write the resource block to match, add an import block pointing at the real ID, run plan to confirm it lines up (no changes), and apply — now it is managed.

import.tf
import {
to = aws_instance.web # the address in your config
id = "i-0abc123def456" # the real, already-existing resource
}
resource "aws_instance" "web" { # must match reality closely enough to plan clean
ami = "ami-0abc123"
instance_type = "t3.micro"
}
# terraform plan → should show 1 to import, 0 to change

moved: refactor without destroying

When you rename a resource or pull it into a module, its state address changes — and by default Terraform reads that as “old address destroyed, new address created,” which for real infrastructure is an outage. A moved block tells Terraform the resource simply has a new address, so it updates state in place with no destroy. This is how you refactor safely, including the count→for_each switch from the last lesson.

moved.tf
moved {
from = aws_instance.web # old address
to = module.web.aws_instance.this # new address after refactor
}
# plan now shows a move, not a destroy/create. (state mv is the imperative equivalent.)
Always plan an import or move before applying
Both operations are about making state match your intent, and the plan is where you verify that. After an import, the plan should show the resource imported with zero changes — if it shows changes, your config does not match reality and applying will modify live infrastructure. After a moved, the plan should show a move, not a destroy/create. Never apply an import or refactor whose plan you have not read carefully; that plan is the safety check.