Terraform Modules Writing Reusable Infrastructure Code
Every experienced Terraform practitioner uses modules. They are the primary way to write infrastructure code that you can reuse across projects, environments, and teams without copying and pasting. This topic explains what modules are, how they work, and why they are the most important organisational concept in Terraform.
What Is a Module
A module is a directory that contains one or more Terraform .tf files. Every Terraform configuration you have written so far is technically a module — specifically, the root module. When you call another directory of Terraform code from your root module, that other directory becomes a child module.
Analogy: Functions in a Programming Language
In a programming language, you write a function once and call it as many times as you need — passing different inputs each time. Modules work exactly the same way. You write the infrastructure pattern once and call it with different inputs: once for development, once for staging, once for production.
The Problem Modules Solve
Without Modules: Repeated Code
project/
dev/
main.tf # VPC + subnets + security groups
variables.tf
staging/
main.tf # Same code, different values
variables.tf
prod/
main.tf # Same code again, different values
variables.tf
Three copies of the same code. A bug fix or improvement means updating all three files.
With Modules: One Source of Truth
project/
modules/
networking/
main.tf # Written once
variables.tf
outputs.tf
dev/
main.tf # Just calls the module with dev values
staging/
main.tf # Just calls the module with staging values
prod/
main.tf # Just calls the module with prod values
Fix a bug in modules/networking/main.tf and all three environments benefit from the fix automatically on next apply.
Module Directory Structure
A well-structured module follows a consistent pattern:
modules/
networking/
main.tf # Resources the module creates
variables.tf # Input variables (what callers must provide)
outputs.tf # Output values (what callers can read back)
README.md # Documentation (optional but recommended)
Writing a Simple Network Module
modules/networking/variables.tf
variable "vpc_cidr" {
description = "CIDR block for the VPC"
type = string
}
variable "environment" {
description = "Deployment environment name"
type = string
}
modules/networking/main.tf
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
tags = {
Name = "${var.environment}-vpc"
Environment = var.environment
}
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, 1)
tags = {
Name = "${var.environment}-public-subnet"
}
}
modules/networking/outputs.tf
output "vpc_id" {
description = "ID of the created VPC"
value = aws_vpc.main.id
}
output "public_subnet_id" {
description = "ID of the public subnet"
value = aws_subnet.public.id
}
Calling a Module
To use the module, add a module block in your root configuration and point its source at the module directory.
module "dev_network" {
source = "./modules/networking"
vpc_cidr = "10.0.0.0/16"
environment = "dev"
}
module "prod_network" {
source = "./modules/networking"
vpc_cidr = "10.1.0.0/16"
environment = "prod"
}
Two module calls. Two complete network setups. One module to maintain.
Diagram: Module Call Flow
Root module (main.tf)
|
|---> module "dev_network" { source = "./modules/networking" }
| |
| v
| modules/networking/
| - aws_vpc.main (dev-vpc)
| - aws_subnet.public (dev-public-subnet)
| |
| v
| Outputs:
| vpc_id = "vpc-0abc"
| public_subnet_id = "subnet-0def"
|
|---> module "prod_network" { source = "./modules/networking" }
|
v
modules/networking/
- aws_vpc.main (prod-vpc)
- aws_subnet.public (prod-public-subnet)
Module Source Types
The source argument supports several source locations:
| Source Type | Example |
|---|---|
| Local path | "./modules/networking" |
| Terraform Registry | "terraform-aws-modules/vpc/aws" |
| GitHub | "github.com/org/repo//modules/network" |
| S3 bucket | "s3::https://s3.amazonaws.com/bucket/module.zip" |
Key Points
- A module is any directory containing
.tffiles; the directory you run Terraform from is the root module. - Modules eliminate repeated code — write infrastructure patterns once, call them many times with different inputs.
- A module uses
variables.tfto accept inputs,main.tfto define resources, andoutputs.tfto expose results. - Call a module with a
moduleblock, settingsourceto the module's path or registry address. - Run
terraform initafter adding or changing a module source to download or re-link it.
