Terraform Modules That Do Not Rot

Infrastructure as Code fixes configuration drift until the code becomes the problem. Four design decisions that determine whether a Terraform estate survives two years.

3 min read
On this page

Infrastructure as Code solves configuration drift right up to the point where the code itself becomes the problem: modules copied between environments until no two are alike, a state file nobody wants to touch, and a plan output long enough that people approve it without reading.

The estates that stay healthy differ from the ones that rot in a handful of decisions, usually made early.

Compose, do not copy

The most common failure starts as a reasonable shortcut. A module nearly fits a new environment, so it gets copied and adjusted. Two years later there are six variants, each with a fix the others never received.

A single module with forty variables is not the answer either. That fails differently, becoming a configuration language nobody can hold in their head. What works is smaller modules composed together, each doing one thing, with the environment-specific part left in the environment.

A useful test: when you fix a bug, how many places need the same fix? If the answer is more than one, the structure is wrong.

Write plan output for the reviewer

A Terraform plan is a code review artefact, and most estates treat it as exhaust.

If every apply produces hundreds of lines of churn, reviewers stop reading. Tags recalculated, timestamps updated, resources that always show as changing. The one destructive change then passes unnoticed, because it looked like all the others.

Getting to a clean plan is real work: pinning things that should not drift, fixing resources that are perpetually out of date, removing the computed values that change on every run. It pays for itself the first time someone catches a replacement that would have dropped a database.

Decide where state lives before you need to

State layout determines your blast radius. One state file for an entire environment means every change risks everything and every apply serialises against every other. Per-resource state means nobody can reason about the whole.

The workable middle is state boundaries drawn around things that change together and are owned by the same people: networking separate from data stores, separate from applications. Cross-boundary references go through data sources or explicit outputs, which also makes coupling visible instead of incidental.

Changing this later is painful. It is worth an hour of discussion at the start.

Test the disaster recovery story

Most teams can describe their disaster recovery plan. Rather fewer have provisioned a complete environment from scratch recently.

The gap between those two is where the surprises are: manual steps nobody documented, a resource created by hand in 2022, a secret that exists only in someone's password manager, an implicit ordering dependency that has always happened to work.

Provision a full environment from nothing, periodically, on real infrastructure. It is the only way to find out whether the code describes your infrastructure or merely resembles it.

The pattern underneath

Each of these is the same decision in different clothes: optimise for the person who reads this in eighteen months, not for the person writing it today.

That person will not have the context you have now. They will have a plan output, a module structure, and a deadline. What you leave them determines whether the estate gets maintained or replaced.