Terraform is easy to get started with and easy to make unmaintainable. The difference between a codebase that scales to a multi-person infra team and one that turns into a plan-and-pray minefield usually comes down to three habits.
Keep root modules thin#
A root module should mostly be a list of module calls and variable wiring, not raw resource blocks. Logic belongs in reusable modules; the root module is configuration, not implementation.
module "payments_vpc" {
source = "../../modules/vpc"
name = "payments-prod"
cidr_block = "10.20.0.0/16"
azs = ["eu-west-1a", "eu-west-1b", "eu-west-1c"]
enable_nat_gateway = true
}
module "payments_eks" {
source = "../../modules/eks"
cluster_name = "payments-prod"
vpc_id = module.payments_vpc.vpc_id
subnet_ids = module.payments_vpc.private_subnet_ids
}Give every module an explicit, minimal interface#
A module’s variables.tf and outputs.tf are its contract. Resist the temptation to pass through every possible AWS provider argument “just in case” — every extra variable is a thing every caller now has to understand, and a thing you can’t change without a breaking-change conversation.
variable "name" {
type = string
description = "Name prefix applied to all resources created by this module."
}
variable "cidr_block" {
type = string
description = "CIDR block for the VPC."
}
output "vpc_id" {
value = aws_vpc.this.id
description = "ID of the created VPC, for wiring into downstream modules."
}Isolate state per environment, not per team#
One state file per environment (dev/staging/prod), not one giant state file for the whole organisation and not one state file per engineer. Remote state with locking (S3 + DynamoDB, or Terraform Cloud) is non-negotiable once more than one person applies changes.
terraform {
backend "s3" {
bucket = "acme-terraform-state"
key = "payments/prod/terraform.tfstate"
region = "eu-west-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}The common thread across all three habits is the same one that shows up in good software architecture generally: make interfaces explicit, keep blast radius small, and don’t let convenience today become someone else’s incident next quarter.


