The problem
I was managing infrastructure across multiple regions a handful of virtual machines, one per region, each running the same service. The Terraform setup had grown the way these things usually do: when I needed a VM in a new region, I copied the folder for an existing region, renamed a few things, and adjusted the values that were different (location, network ranges, naming).
It worked. Until it didn't.
Every region had its own full copy of:
- The provider block
- The resource definitions (VM, network interface, disk, public IP, etc.)
- The output block
Three regions in, I had three nearly-identical folders, each a few hundred lines, differing in maybe ten lines of actual content. Any time I wanted to change something structural add a tag, adjust a disk size, fix a naming convention I had to make that change in every single folder, and hope I didn't miss one or introduce a subtle inconsistency between them.
This is the classic copy-paste infrastructure trap: it feels fast in the moment (just copy the folder!) but every region you add makes the next change more expensive, not less.
The pattern: separate "what's shared" from "what's different"
The fix was to stop treating each region as its own Terraform project and instead treat the infrastructure definition as shared code, with only the differences expressed as data.
Concretely, that meant three layers:
1. A shared module one Terraform module containing the actual resource definitions (VM, networking, disk, etc.), written with input variables for everything that varies between regions: location, name suffix, network ranges, and so on.
2. One shared entry point a single root configuration that calls the module, rather than one root configuration per region.
3. A small config file per region instead of a full copy of the module, each region gets a short file (five or six lines) specifying just its own values: which cloud location to deploy into, what to name things, what network ranges to use.
Adding a new region now means adding one small config file. No new Terraform resource code, no new pipeline definition, nothing to copy and rename.
Why this matters beyond "less typing"
The obvious win is less duplication. The less obvious win is correctness:
- A structural fix only needs to happen once. Change the module, every region picks it up. Before, a structural change meant editing N folders and trusting yourself to get all N edits identical.
- Drift between regions becomes visible. With copy-pasted folders, two regions can quietly diverge over time as one gets a fix the other doesn't. With a shared module, divergence can only happen in the small config file which is short enough to diff at a glance.
- Review gets easier. A pull request adding a new region is now a five-line diff to a new config file, not a few hundred lines of near-duplicate resource code that's hard to review carefully.
What I kept separate on purpose
One thing worth calling out: sharing the code doesn't mean sharing everything. Each region still keeps:
- Its own Terraform state file, so a mistake applying to one region can't touch another region's resources.
- Its own deployment approval gate in the pipeline, so promoting a change to one region doesn't silently promote it everywhere.
The pattern is "share the how, isolate the where it lands." Sharing a module is safe. Sharing a blast radius is not.
The general lesson
If you find yourself about to copy a folder to stand up a new instance of something a new region, a new environment, a new tenant that's usually the signal to stop and ask: what's actually different here? Usually the answer is "not much" a handful of values. Everything else can be a shared module, parameterized by those values.
Copy-paste feels like the fast path. It's fast for the first copy. It gets slower and riskier every time after that.
Top comments (0)