CoursesTerraformIntermediate

Modules: reusable infrastructure

DRY infrastructure you can share.

Intermediate14 min · lesson 6 of 15

Once you have written the same VPC-plus-subnets-plus-security-groups block three times, you want to write it once and reuse it. A module is exactly that: a folder of .tf files with inputs (variables) and outputs, called from other configurations. Every Terraform configuration is already a module — the root module — and calling other modules is how you build infrastructure from reusable, tested components instead of copy-pasted blocks.

main.tf (root)
module "network" {
source = "./modules/network" # a local module folder
cidr = "10.0.0.0/16"
environment = var.environment
}
module "web" {
source = "./modules/web"
subnet_ids = module.network.private_subnet_ids # wire modules together via outputs
}

Inputs, outputs, and wiring

A module declares variables for what its caller must supply and outputs for what it exposes back. You pass values in as arguments on the module block, and read results as module.<name>.<output>. That input/output contract is what lets you wire modules together — the network module outputs subnet IDs, the web module takes them as input — and lets you change a module’s internals without touching its callers, as long as the contract holds.

Where modules come from

The source argument can point at a local path (./modules/network), the public Terraform Registry (a versioned, community or vendor module), a private registry, or a Git URL. Registry and Git sources take a version, which you should always pin — an unpinned module can change under you exactly like an unpinned provider. Public modules like terraform-aws-modules/vpc are battle-tested and a good way to avoid reinventing a correct VPC.

main.tf
module "vpc" {
source = "terraform-aws-modules/vpc/aws" # public registry module
version = "5.8.1" # PIN it
name = "prod-vpc"
cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
}
Keep modules focused, and pin their versions
Two module pitfalls. First, the god-module: a single module with fifty inputs that tries to build everything is as hard to reuse as no module at all — keep each module to one clear job (a network, a service, a database). Second, unpinned sources: a registry or Git module without a version can change between applies and silently alter your infrastructure. Pin every external module to an exact version and bump it deliberately, the same supply-chain discipline as providers.