Terraform Modules: Reusable Infrastructure Components
Terraform modules are self-contained packages of Terraform configuration that manage a group of related infrastructure resources as a single reusable unit.
What You'll Learn
In this tutorial, you will learn how to create Terraform modules, use input variables and outputs to interface with modules, call modules from configurations, and publish modules for team reuse.
Why It Matters
Modules eliminate duplication and enforce consistency. Instead of copying resource blocks across environments, you define the pattern once and reuse it. Teams using modules report 60 percent faster infrastructure provisioning and fewer configuration errors.
Real-World Use
DodaTech maintains a library of Terraform modules for VPC, EC2, RDS, and load balancers. Durga Antivirus Pro's platform team composes these modules to spin up complete environments in minutes, with consistent security groups, tagging, and encryption settings.
Module Structure
A module is a directory containing Terraform files. It follows the same structure as a root module but is designed for reuse:
# modules/vpc/main.tf
resource "aws_vpc" "this" {
cidr_block = var.cidr_block
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = var.name
Environment = var.environment
ManagedBy = "Terraform"
}
}
resource "aws_subnet" "public" {
count = length(var.public_subnet_cidrs)
vpc_id = aws_vpc.this.id
cidr_block = var.public_subnet_cidrs[count.index]
map_public_ip_on_launch = true
availability_zone = var.availability_zones[count.index]
}
# modules/vpc/variables.tf
variable "name" {
description = "Name prefix for VPC resources"
type = string
}
variable "environment" {
description = "Deployment environment"
type = string
}
variable "cidr_block" {
description = "CIDR block for the VPC"
type = string
}
variable "public_subnet_cidrs" {
description = "CIDR blocks for public subnets"
type = list(string)
}
variable "availability_zones" {
description = "Availability zones for subnets"
type = list(string)
}
# modules/vpc/outputs.tf
output "vpc_id" {
description = "The VPC ID"
value = aws_vpc.this.id
}
output "public_subnet_ids" {
description = "List of public subnet IDs"
value = aws_subnet.public[*].id
}
Calling a Module
Root configurations reference modules from the local filesystem, registry, or Git:
# main.tf
module "vpc" {
source = "./modules/vpc"
name = "dodatech-main"
environment = "production"
cidr_block = "10.0.0.0/16"
public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"]
availability_zones = ["us-east-1a", "us-east-1b"]
}
module "web" {
source = "./modules/ec2"
name = "web-server"
environment = "production"
subnet_id = module.vpc.public_subnet_ids[0]
instance_type = "t3.medium"
}
Expected output: Terraform creates the VPC first (from the module), then creates the EC2 instance using the VPC's subnet IDs. Module outputs feed into other modules as inputs.
Module Registry
The Terraform Registry hosts thousands of public modules:
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.0.0"
name = "dodatech-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"]
public_subnets = ["10.0.3.0/24", "10.0.4.0/24"]
enable_nat_gateway = true
enable_vpn_gateway = false
tags = {
Environment = "production"
}
}
Expected output: Running <a href="/devops/terraform/">terraform</a> init downloads the VPC module from the registry. The module creates a complete VPC with subnets, NAT gateway, and route tables.
Module Versioning
Pin module versions to prevent unexpected changes:
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = ">= 5.0, < 6.0"
name = "dodatech-vpc"
cidr = "10.0.0.0/16"
}
Expected output: Terraform resolves the module version constraint at init time. The lock file records the exact version used.
Module Composition
Modules compose to build complete architectures:
module "network" {
source = "./modules/vpc"
# ... network inputs
}
module "database" {
source = "./modules/rds"
subnet_ids = module.network.private_subnet_ids
# ... database inputs
}
module "application" {
source = "./modules/ecs"
vpc_id = module.network.vpc_id
db_address = module.database.endpoint
# ... application inputs
}
Expected output: Three modules connected through outputs. Terraform builds a dependency graph and creates resources in the correct order: network, database, application.
Common Mistakes
1. Module Path Errors
Using a relative path that does not exist or forgetting to initialize modules with <a href="/devops/terraform/">terraform</a> init.
2. Not Pinning Module Versions
Calling source = "<a href="/devops/terraform/">terraform</a>-aws-modules/vpc/aws" without version constraints causes unpredictable upgrades.
3. Exposing Internal Resource Details
Outputting raw resource IDs creates tight coupling. Output only what consumers need.
4. Hardcoding Values Inside Modules
Modules should accept all configurable values through variables. Hardcoded values make modules unusable in different contexts.
5. Nesting Modules Too Deep
Deep module nesting (five-plus levels) makes debugging difficult. Prefer shallow, composable module structures.
Practice Questions
1. What is a Terraform module? A self-contained directory of Terraform configuration files that manages a group of related resources as a reusable unit.
2. How do you pass values into a module?
Module input variables are set as arguments in the module block, for example module "vpc" { source = "./vpc" cidr = "10.0.0.0/16" }.
3. Why should you pin module versions? Version pinning prevents unexpected upgrades that could introduce breaking changes or alter infrastructure behavior.
4. What is the difference between a root module and a child module?
Every Terraform configuration has a root module (the current working directory). Child modules are called from root or other modules using the module block.
5. Challenge: Create a module that provisions an EC2 instance with a security group. Call it from three different environments with different instance types and tag values.
Mini Project: Reusable Web Module
Create a web-server module that includes an EC2 instance, security group opening ports 80 and 443, and an Elastic IP. Call this module from root configurations for dev, staging, and production with different instance sizes.
Related Concepts
What's Next
Build reusable Terraform modules to standardize your infrastructure patterns, then use Workspaces to manage multiple environments.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro