Terraform

Terraform Architecture for Growing Engineering Teams

How to structure Terraform state, modules and review workflow so infrastructure changes stay routine as the number of contributors grows.

9 min read

A Terraform codebase that works for two engineers usually fails for twelve. The failure mode is predictable: one enormous state file, plans that take fifteen minutes, and a review culture where nobody reads the diff because it is unreadable.

Split state by blast radius

The right unit of state is the set of resources you are willing to have fail together. In practice that means separating foundational layers (accounts, networking, identity) from platform layers (clusters, databases, shared services) and from application layers. Cross-layer references go through remote state data sources or, better, through explicitly published outputs so the coupling is visible.

infra/
  foundation/    # accounts, VPCs, IAM baseline — changes rarely
  platform/      # clusters, databases, shared services
  services/      # per-service infrastructure, one state each
  modules/       # versioned, reusable, no environment assumptions

Modules should encode decisions, not wrap resources

A module that takes thirty variables and passes them to one resource adds indirection without adding value. A good module encodes an opinion: 'this is how we run a Postgres database' — with backup, monitoring, parameter group and network placement already decided, and only the genuinely variable parts exposed.

Environments as inputs, not copies

Copy-pasted environment directories drift on day one. Environments should be the same composition of modules with different input values. If production needs a structurally different topology from staging, that difference should be a documented decision, not an accident of copy-paste history.

The pull request is the control point

  • Plan runs automatically on every PR and is posted as a comment.
  • Policy checks (Checkov, OPA or Sentinel) block known-dangerous changes.
  • Cost estimation on the diff, so a €4,000/month change is visible before merge.
  • Apply happens from CI after merge, never from a laptop.
  • Destroy operations require an explicit, separately approved path.

Treat drift as a defect

Run a scheduled plan against every state and alert on non-empty output. Drift means either someone changed something by hand — a process problem — or a provider is fighting your configuration — a code problem. Both are worth knowing about before the next real change.

Version and release modules

Once more than one team consumes a module, pin versions and release deliberately. Unversioned shared modules mean an unrelated team's change can appear in your next plan, which is the fastest way to make engineers stop trusting the tooling.

Want this assessed against your own environment?

The Aspiron Infrastructure Review applies exactly this analysis to your cloud, delivery pipeline and cost — 2 weeks · fixed scope · no long-term commitment.

Book an Infrastructure Review