Reusable Module Design: The Clean Interface Contract
Standard layout: main.tf, variables.tf, outputs.tf, versions.tf, and README.md.
Variables are function parameters, outputs are return values. Keep internal details private.
Pin shared modules using git tags and semantic versioning: '?ref=v2.1.0'.
The Big Idea
In software engineering, you do not write an entire application in a single 10,000-line file: you break it into packages, classes, and functions with well-defined interfaces. The exact same architectural principle applies to Terraform through Modules.
A Terraform module is simply a directory containing .tf files. Every project has at least one module (the root module). Child Modules are reusable components that encapsulate complex infrastructure patterns, like a hardened VPC, an EKS cluster, or a secure RDS database.
The key to elite module design is treating the module as an immutable public API: variables.tf defines the inputs, outputs.tf defines the exported outputs, and main.tf contains private implementation details that callers do not need to understand.
Jargon Buster: Plain-English Translations
- Root Module: The top-level working directory where the 'terraform' CLI is executed.
- Child Module: A module called from another module using a 'module {}' block to reuse infrastructure patterns.
- Module Source: The location where the module code resides: local relative path, Git repository, or Terraform Registry.
- Semantic Versioning (SemVer): Pinning module versions using 'MAJOR.MINOR.PATCH' tags to ensure backward-compatible deployments.
Enterprise Module Structure & Consumer Invocation
# Standard Module Directory Layout:
# modules/aws-secure-vpc/
# ├── README.md # Human documentation and architecture diagrams
# ├── versions.tf # Required terraform and provider versions
# ├── variables.tf # Inputs, constraints, and validation
# ├── main.tf # Resource declarations
# └── outputs.tf # Exported attributes (VPC ID, Subnets)
# In Consumer Root Module: main.tf
module "production_vpc" {
# Remote git source pinned to an exact immutable release tag
source = "git::https://github.com/myorg/terraform-aws-vpc.git?ref=v1.4.0"
vpc_cidr = "10.200.0.0/16"
environment = "production"
enable_nat_gateway = true
single_nat_gateway = false # Multi-AZ High Availability
availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"]
}
# Consume module outputs directly in downstream resources
resource "aws_security_group" "app" {
name = "app-firewall"
vpc_id = module.production_vpc.vpc_id # Module output reference
}
Beginner Traps & Gotchas
1. Referencing Git master or main branch without version pinning
The Gotcha: Setting source to 'git::https://...git' without a '?ref=' tag, causing unexpected breaking changes when someone merges to main.
The Fix: Always pin shared remote modules to explicit immutable release tags, e.g. '?ref=v1.2.0'.
2. Hardcoding provider blocks inside child modules
The Gotcha: Declaring 'provider "aws" {}' inside a reusable child module, creating provider conflicts for callers.
The Fix: Never declare provider blocks inside child modules. Only declare provider requirements in 'terraform.required_providers' and let the root module pass down provider instances.