A Terraform Module is a container for multiple resources that are used together. If you find yourself writing the same VPC, Kubernetes cluster, or database configuration across 5 different services, package it into a module.
100%
Rendering interactive visual diagram...
The Standard Module File Structure
A clean, production-grade module repository follows this exact structure:
Project Structure
modules/vpc_network/
āāā README.md
# Documentation, inputs, and outputs tableāāā main.tf
# Core resource declarationsāāā variables.tf
# Input variable definitions with validationsāāā outputs.tf
# Exported values (VPC ID, subnet IDs)āāā versions.tf
# Minimum required Terraform and provider versionsBuilding a Complete Production VPC Module
Let's write a real-world, reusable VPC module:
1. modules/vpc_network/variables.tf
hcl
variable "vpc_cidr" {
type = string
description = "The CIDR block for the VPC"
default = "10.0.0.0/16"
}
variable "availability_zones" {
type = list(string)
description = "List of AWS availability zones to deploy subnets into"
}
variable "public_subnet_cidrs" {
type = list(string)
description = "CIDR blocks for public subnets"
}
variable "private_subnet_cidrs" {
type = list(string)
description = "CIDR blocks for private subnets"
}
variable "environment" {
type = string
description = "Deployment environment name (e.g. dev, prod)"
}
2. modules/vpc_network/main.tf
hcl
# VPC Resource
resource "aws_vpc" "this" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.environment}-vpc"
Environment = var.environment
ManagedBy = "Terraform"
}
}
# Internet Gateway for Public Internet Access
resource "aws_internet_gateway" "this" {
vpc_id = aws_vpc.this.id
tags = {
Name = "${var.environment}-igw"
}
}
# Public Subnets (Iterate using count)
resource "aws_subnet" "public" {
count = length(var.public_subnet_cidrs)
vpc_id = aws_vpc.this.id
cidr_block = var.public_subnet_cidrs[count.index]
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = {
Name = "${var.environment}-public-subnet-${count.index + 1}"
Type = "Public"
}
}
# Route Table for Public Subnets
resource "aws_route_table" "public" {
vpc_id = aws_vpc.this.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.this.id
}
tags = {
Name = "${var.environment}-public-rt"
}
}
resource "aws_route_table_association" "public" {
count = length(aws_subnet.public)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
3. modules/vpc_network/outputs.tf
hcl
output "vpc_id" {
value = aws_vpc.this.id
description = "The ID of the provisioned VPC"
}
output "public_subnet_ids" {
value = aws_subnet.public[*].id
description = "List of IDs for all public subnets"
}
output "vpc_cidr" {
value = aws_vpc.this.cidr_block
description = "The primary CIDR block of the VPC"
}
Consuming Your Module in Root Configurations
Now, any team in your company can provision a complete production network in 10 lines of HCL:
hcl
# environments/production/main.tf
module "production_vpc" {
source = "../../modules/vpc_network"
environment = "production"
vpc_cidr = "10.100.0.0/16"
availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"]
public_subnet_cidrs = ["10.100.1.0/24", "10.100.2.0/24", "10.100.3.0/24"]
private_subnet_cidrs = ["10.100.10.0/24", "10.100.20.0/24", "10.100.30.0/24"]
}
# Feed module outputs directly into application servers
resource "aws_security_group" "web_sg" {
name = "production-web-sg"
description = "Allow inbound HTTP"
vpc_id = module.production_vpc.vpc_id # Directly referencing module output!
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
Module Sources: Where Can Modules Live?
Terraform supports loading modules from multiple sources:
hcl
# 1. Local Filesystem
source = "./modules/vpc"
# 2. GitHub with semantic version tag (Best for shared company libraries!)
source = "git::https://github.com/my-org/terraform-aws-vpc.git?ref=v2.1.0"
# 3. Public Official Terraform Registry
source = "terraform-aws-modules/vpc/aws"
version = "5.8.1"