CoursesInfrastructure as Code & automationIntermediate · Terraform at scale

Modules: reusable infrastructure

DRY infrastructure you can share.

Intermediate14 min · lesson 9 of 23

Once you write more than a little Terraform, you find yourself copying the same blocks — a VPC here, the same three resources there — and that duplication is exactly what modules eliminate. A module is a reusable, parameterized package of Terraform: a folder of .tf files that takes inputs (variables), creates resources, and returns outputs. You call it like a function, passing different inputs each time, so one well-written network module builds dev, staging, and prod networks from the same reviewed code. Modules are how Terraform scales from a script to a maintainable codebase.

main.tf (calling a module)
module "network" {
source = "./modules/network" # a local module (or a registry/Git URL)
cidr = "10.0.0.0/16" # inputs (variables)
env = "prod"
}
module "web" {
source = "./modules/web"
subnet_id = module.network.private_subnet_id # use a module OUTPUT as input
env = "prod"
}

Inputs, outputs & the interface

A module’s value is its interface: variables declare what you can configure, and outputs declare what it exposes to the caller. Inside, the module creates resources using those variables; outside, callers pass inputs and consume outputs (like module.network.private_subnet_id above) to wire modules together. A good module has a small, clear set of inputs and outputs — it hides the complexity of the resources it manages behind a simple contract, exactly like a function hides its implementation. That encapsulation is what makes infrastructure reusable and reviewable.

modules/network/variables.tf + outputs.tf
# variables.tf — the inputs the module accepts
variable "cidr" { type = string }
variable "env" { type = string }
# outputs.tf — what the module exposes to callers
output "private_subnet_id" {
value = aws_subnet.private.id
}

Module sources and the registry

Modules can live locally (source = "./modules/network"), in a Git repository, or in the public Terraform Registry, which hosts thousands of community and verified modules for common infrastructure. Reusing a well-maintained registry module (a VPC, an EKS cluster) can save enormous effort — but it also means running someone else’s code against your cloud, so you pin the module version and, for anything important, review or vet it. This is the same trust decision as any dependency, which is why module supply chain gets its own advanced lesson.

Pin module versions, especially from the registry
A module referenced without a version — particularly one from the public registry or a Git branch — can change under you, altering or destroying infrastructure on your next apply. Always pin: version = "5.1.2" for registry modules, or a specific tag/commit for Git sources. An unpinned module is untrusted, mutable code with the power to change your production infrastructure; treat module versions with the same rigor as provider versions and application dependencies.