A well-structured module has:
- Single responsibility — one module per logical concern (VPC, EKS, RDS, etc.)
- Clear inputs and outputs — every variable and output has a description
- Sensible defaults — variables have defaults where reasonable
- Versioned — pin module versions in callers (
version = "~> 5.0") - Tested in lower environments first — never apply to prod without testing in dev/staging
- Stateless code, stateful state — code doesn't change between environments, only variable values do
A typical module structure:
modules/vpc/
├── main.tf # resource blocks
├── variables.tf # variable blocks
├── outputs.tf # output blocks
├── versions.tf # terraform + required_providers
└── README.md # usage docs