Lesson 13 of 16
Section 4: Enterprise Modular Architecture, GitOps & Production Security40 min read & walkthrough

Reusable Module Design: The Clean Interface Contract

01 / ANATOMY
Module Standard

Standard layout: main.tf, variables.tf, outputs.tf, versions.tf, and README.md.

02 / CONTRACT
Public API

Variables are function parameters, outputs are return values. Keep internal details private.

03 / VERSIONING
Git Pinning

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.