Almost every engagement starts the same way: there is critical infrastructure that was built by hand in a console two years ago, the person who built it has left, and nobody dares touch it. The job is to bring it under Terraform without causing the outage everyone is afraid of. This is mostly archaeology. You are reverse-engineering decisions, not writing greenfield code.
Import in a corner first, never against prod state
The cardinal rule: your first import attempt runs against a throwaway state, in isolation, where a mistake costs nothing. `terraform import` only writes state, it does not write configuration, so the dangerous moment is the first plan afterward. If your HCL does not match reality exactly, that plan wants to destroy and recreate the resource. For a production database that is a career-defining diff to apply by accident.
Generate config, then fight the plan to zero
Modern Terraform's `import` blocks with `-generate-config-out` get you a rough draft of the HCL automatically, which beats hand-typing 40 arguments. But generated config is a starting point, not an answer. You then iterate: plan, see what it wants to change, fix the HCL to match the real resource, plan again. You are done when the plan is a clean no-op.
import {
to = aws_db_instance.legacy_orders
id = "orders-prod-db"
}
# terraform plan -generate-config-out=generated.tf
# then iterate the generated HCL until plan shows: No changes.- Work resource by resource. Importing 200 things in one block and getting a 200-resource diff is unreviewable.
- Expect to lose. Some attributes (auto-generated IDs, default tags, computed fields) will fight you - use `lifecycle { ignore_changes }` rather than forcing them.
- Snapshot first. Take a real backup of any stateful resource before the first apply, no matter how confident the plan looks.
- Import the dependencies too. A subnet imported without its route table is half a picture and will drift immediately.
You are done importing when the plan is a clean no-op. Anything else is a destroy waiting to happen.
The order that keeps you safe
We import from the bottom up: foundational, stateless things first (VPCs, subnets, security groups), then stateful things last (databases, persistent volumes). By the time you reach the scary stateful resources, you have built confidence and a clean state around them, and the riskiest applies are the smallest ones. We also do the first real apply during a low-traffic window with a rollback plan written down, even when we are certain the plan is a no-op. Certainty is exactly the feeling that precedes the outage.
One cultural note: adopting click-ops infrastructure is a good moment to also close the door behind you. If the same console access that created the mess is still wide open, you will be re-importing the same drift next quarter. Tightening who can change what by hand is part of finishing the job.