migrating 500 existing resources to Terraform
The real experiences nobody tells you about..!

I used to think migrating existing infrastructure into Terraform was… basically a big import marathon.
Like, “Cool, we’ll just run terraform import 500 times, write some HCL, and boom—Infrastructure as Code.”
(Spoiler: that’s not what happens. At all.)
My misunderstanding came from treating Terraform like a backup tool instead of what it really is: a decision-making system. I thought the goal was to mirror reality perfectly. The aha! moment hit much later—probably after my third late-night state mismatch—when I realized this:
Terraform doesn’t want to describe what exists. It wants to describe what should exist next.
Once that clicked, migrating 500 resources stopped being terrifying and started being… methodical. Still annoying. But survivable.
The real problem nobody tells you about
When we say “migrate 500 resources,” what we’re really saying is:
- 500 resources
- across multiple creation styles
- owned by different teams
- with inconsistent naming
- and at least one thing nobody remembers creating (there’s always one)
The mistake we make early is trying to Terraform everything at once. That’s how you end up with a 3,000-line main.tf and trust issues.
Instead, we slow down. We ask better questions. We decide what actually belongs together.
Step 1: Stop thinking in resources. Start thinking in boundaries.
Here’s the uncomfortable truth: Not all 500 resources deserve to be imported.
Some are:
- legacy experiments
- one-off hotfixes
- “temporary” things from 2019
- or already obsolete but still running (because… reasons)
Before we touch Terraform, we group things by decision boundary:
- This network is managed together
- This app stack is deployed together
- This shared service changes slower than everything else
(If two resources are never changed together, they probably shouldn’t live in the same state.)
Pro-tip: If a change requires three approvals and a meeting, it’s a different Terraform boundary than something you tweak daily.
Step 2: Write Terraform like the resources don’t exist yet
This feels backwards the first time.
We don’t import first. We write clean Terraform first.
We define:
- naming conventions (even if reality disagrees)
- variables that make sense now
- modules that express intent, not history
Yes, this means our HCL won’t match the real world initially. That’s okay. Terraform isn’t a mirror—it’s a map.
(And honestly, this is where you fix years of silent regret.)
Step 3: Import surgically, not heroically
Now we import—but carefully.
One stack. One resource type. Small batches.
After each import:
- we run
terraform plan - we expect some drift
- we decide whether to fix Terraform or fix the resource
This is the key mindset shift:
Drift is not failure. Drift is information.
Pro-tip: If terraform plan shows 40 changes right after import, stop. Something’s wrong. Don’t “apply and hope.”
Step 4: Expect emotional damage (and plan for it)
Some things will fight us:
- tags that were added manually
- defaults we didn’t know existed
- fields Terraform insists on managing now
This is where we choose pragmatism over purity.
We use:
lifecycle { ignore_changes = [...] }(sparingly)- explicit defaults to calm the plan
- documentation for future-us (who will forget why this exists)
(Also: drink your coffee before debugging state issues, not after.)
Step 5: Lock it down and move forward
Once a stack is stable:
- we protect the state
- we restrict manual changes
- we make Terraform the only way forward
This is the moment the migration actually succeeds—not when the last resource is imported, but when humans stop bypassing the workflow.
And yes, someone will still try to “just quickly change it in the console.” That’s a different conversation.
The thing I wish someone told me earlier
Migrating 500 resources isn’t a Terraform problem.
It’s a clarity problem.
Terraform just forces us to answer questions we’ve been avoiding:
- What belongs together?
- What changes together?
- What do we actually care about controlling?
Once we answer those, the commands are… boring. And boring is good.
So I’ll leave you with this (and I genuinely mean it): Happy Terraforming!! If you had to rebuild your current infrastructure from scratch tomorrow—what parts would you not bring back into Terraform, and why are they still running today?
#terraform #hashicorp
Disclaimer: The views and opinions expressed in this post are my own and do not reflect the views of my employer.





