Back to Blog
TerraformIaCDevOpsAWS
Building Reusable Terraform Modules: A Complete Guide
John DoeJanuary 10, 20242 min read
Terraform modules are the key to writing maintainable, reusable infrastructure code. In this guide, I'll walk you through creating effective Terraform modules.
What Are Terraform Modules?
A module is a container for multiple resources that are used together. Every Terraform configuration has at least one module, known as the root module.
Module Structure
A well-organized module follows this structure:
modules/
└── vpc/
├── main.tf
├── variables.tf
├── outputs.tf
├── versions.tf
└── README.md
Creating a VPC Module
Let's create a reusable VPC module for AWS:
variables.tf
variable "name" {
description = "Name prefix for resources"
type = string
}
variable "cidr_block" {
description = "CIDR block for the VPC"
type = string
default = "10.0.0.0/16"
}
variable "availability_zones" {
description = "List of availability zones"
type = list(string)
}
variable "enable_nat_gateway" {
description = "Enable NAT Gateway for private subnets"
type = bool
default = true
}
main.tf
resource "aws_vpc" "main" {
cidr_block = var.cidr_block
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.name}-vpc"
}
}
resource "aws_subnet" "public" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.cidr_block, 8, count.index)
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = {
Name = "${var.name}-public-${count.index + 1}"
Type = "public"
}
}
resource "aws_subnet" "private" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.cidr_block, 8, count.index + 10)
availability_zone = var.availability_zones[count.index]
tags = {
Name = "${var.name}-private-${count.index + 1}"
Type = "private"
}
}
outputs.tf
output "vpc_id" {
description = "The ID of the VPC"
value = aws_vpc.main.id
}
output "public_subnet_ids" {
description = "List of public subnet IDs"
value = aws_subnet.public[*].id
}
output "private_subnet_ids" {
description = "List of private subnet IDs"
value = aws_subnet.private[*].id
}
Using the Module
module "vpc" {
source = "./modules/vpc"
name = "production"
cidr_block = "10.0.0.0/16"
availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"]
enable_nat_gateway = true
}
output "vpc_id" {
value = module.vpc.vpc_id
}
Best Practices
- Version your modules using semantic versioning
- Document everything with clear README files
- Use validation to catch errors early
- Keep modules focused on a single responsibility
- Test your modules with tools like Terratest
Conclusion
Well-designed modules save time, reduce errors, and make your infrastructure code a joy to work with. Start small and iterate!