Skip to main content

Command Palette

Search for a command to run...

migrating 500 existing resources to Terraform

The real experiences nobody tells you about..!

Updated
4 min read
migrating 500 existing resources to Terraform

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.

The HashiCorp Hustle

Part 2 of 12

"The HashiCorp Hustle" dives deep into the world of HashiCorp tools, empowering you to build, secure, and connect your infrastructure with confidence.

Up next

Hybrid Cloud in 2026

Fewer Tools, Stronger Opinions