GGoldwater.dev
All articles
Platform & DevOps9 min read·

Infrastructure as Code That Survives Contact With a Team

IaC promises reproducible infrastructure, then quietly rots into a pile of copy-pasted modules nobody trusts. Here's how to keep Terraform (or its cousins) maintainable as the team and the estate grow.

Infrastructure as code is one of the highest-leverage practices in modern engineering — and one of the easiest to do badly. The failure mode is rarely dramatic. It's slow: a module gets copied because reuse felt risky, state files multiply, and within a year nobody is confident what a plan will actually do in production.

Treat your IaC like application code

The teams that succeed with Terraform, Pulumi, or CloudFormation treat infrastructure code with the same discipline as their app: code review, a CI pipeline, linting, and tests. The teams that struggle treat it as a pile of scripts that one person runs from their laptop.

  • Every change goes through a pull request and a plan that a human reads.
  • Run plan in CI on every PR; apply only from a protected pipeline, never a laptop.
  • Lint and format automatically (tflint, terraform fmt) so reviews discuss intent, not whitespace.

Structure state to match blast radius

A single monolithic state file is a foot-gun: one mistake can ripple across your entire estate, and plans get slow enough that people stop reading them. Split state along boundaries of change and ownership — per environment, per team, or per bounded service.

The right size for a state file is the smallest unit you'd be comfortable applying on a Friday afternoon.

Modules are an interface, not a copy button

A good module hides complexity behind a small, stable set of inputs. If callers have to understand the module's internals to use it, it isn't abstracting anything. Version your modules, document their inputs, and resist the urge to add a flag for every edge case — sometimes two clear modules beat one configurable monster.

hcl
module "service" {
  source  = "git::https://…/modules/service?ref=v2.3.0"
  name    = "checkout"
  cpu     = 512
  memory  = 1024
  scaling = { min = 2, max = 20 }
}

Make drift visible

Manual changes in the console are inevitable during incidents. What matters is catching them before they compound. Schedule a regular plan that alerts on unexpected diffs, so reality and code never drift far enough apart to become scary to reconcile.

WG

Wes Goldwater

Director of Engineering at Prosigliere · writing the no-hype playbook for cloud & AI.

Keep reading