Skip to main content

Terraform Modules

 

Terraform Modules: Building Reusable Infrastructure

📅 Published: August 2026
⏱️ Estimated Reading Time: 15 minutes
🏷️ Tags: Terraform, Modules, Infrastructure as Code, Reusability


Introduction: What is a Terraform Module?

A module is a container for multiple resources that are used together. It's like a function in programming—you define it once, give it a clear interface, and reuse it everywhere.

Instead of copying and pasting the same infrastructure code across multiple projects, you package it into a module and reuse it.

Think of modules as blueprints:

  • A VPC module might contain VPC, subnets, route tables, and internet gateways

  • A web server module might contain EC2 instances, security groups, and load balancers

  • A database module might contain RDS instances, parameter groups, and security groups


Part 1: Why Use Modules?

Without ModulesWith Modules
Copy-paste same code everywhereDefine once, reuse everywhere
50 lines repeated 10 times50 lines in module, 5 lines to call it
Change requires updating 10 placesChange requires updating 1 place
Hard to share with teamEasy to share and version
Inconsistent implementationsStandardized implementations
hcl
# ❌ Without modules - duplicated everywhere
resource "aws_vpc" "prod" {
  cidr_block = "10.0.0.0/16"
  tags = { Name = "prod-vpc" }
}
# ... 50 more lines

resource "aws_vpc" "dev" {
  cidr_block = "10.1.0.0/16"
  tags = { Name = "dev-vpc" }
}
# ... 50 more lines

# ✅ With modules - one call per environment
module "prod" {
  source   = "./modules/vpc"
  name     = "prod"
  cidr     = "10.0.0.0/16"
}

module "dev" {
  source   = "./modules/vpc"
  name     = "dev"
  cidr     = "10.1.0.0/16"
}

Part 2: Module Structure

The Standard Layout

text
modules/
└── s3-bucket/
    ├── main.tf       # Resource definitions
    ├── variables.tf  # Input variables
    ├── outputs.tf    # Output values
    ├── README.md     # Documentation
    └── examples/     # Usage examples

variables.tf

hcl
# modules/s3-bucket/variables.tf
variable "bucket_name" {
  description = "Name of the S3 bucket"
  type        = string
}

variable "environment" {
  description = "Deployment environment"
  type        = string
  
  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Environment must be dev, staging, or prod."
  }
}

variable "versioning_enabled" {
  description = "Enable bucket versioning"
  type        = bool
  default     = false
}

variable "tags" {
  description = "Tags to apply to resources"
  type        = map(string)
  default     = {}
}

main.tf

hcl
# modules/s3-bucket/main.tf
resource "aws_s3_bucket" "this" {
  bucket = "${var.bucket_name}-${var.environment}-${random_string.suffix.result}"
  
  tags = merge({
    Environment = var.environment
    ManagedBy   = "Terraform"
  }, var.tags)
}

resource "aws_s3_bucket_versioning" "this" {
  count = var.versioning_enabled ? 1 : 0
  
  bucket = aws_s3_bucket.this.id
  versioning_configuration {
    status = "Enabled"
  }
}

resource "random_string" "suffix" {
  length  = 6
  special = false
  upper   = false
}

outputs.tf

hcl
# modules/s3-bucket/outputs.tf
output "bucket_id" {
  description = "Name of the bucket"
  value       = aws_s3_bucket.this.id
}

output "bucket_arn" {
  description = "ARN of the bucket"
  value       = aws_s3_bucket.this.arn
}

output "bucket_name" {
  description = "Full bucket name with suffix"
  value       = aws_s3_bucket.this.bucket
}

output "bucket_regional_domain_name" {
  description = "Regional domain name of the bucket"
  value       = aws_s3_bucket.this.bucket_regional_domain_name
}

Part 3: Using Modules

Local Modules

hcl
# root/main.tf
module "logs_bucket" {
  source   = "./modules/s3-bucket"
  bucket_name = "logs"
  environment = "prod"
  versioning_enabled = true
  tags = {
    Purpose = "Application logs"
  }
}

module "backup_bucket" {
  source   = "./modules/s3-bucket"
  bucket_name = "backups"
  environment = "prod"
  versioning_enabled = true
  tags = {
    Purpose = "Backup storage"
  }
}

# Reference outputs
output "logs_bucket_arn" {
  value = module.logs_bucket.bucket_arn
}

Remote Modules (Terraform Registry)

hcl
# From the public Terraform Registry
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.0.0"
  
  name = "my-vpc"
  cidr = "10.0.0.0/16"
  
  azs             = ["us-west-2a", "us-west-2b", "us-west-2c"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
}

Remote Modules (Git)

hcl
# From a Git repository
module "vpc" {
  source = "git::https://github.com/company/terraform-aws-vpc.git?ref=v1.2.0"
  
  name = "my-vpc"
  cidr = "10.0.0.0/16"
}

# From a Git repository with SSH
module "vpc" {
  source = "git::ssh://git@github.com/company/terraform-aws-vpc.git?ref=v1.2.0"
}

Part 4: Module Composition

Modules can call other modules, building complex infrastructure from reusable pieces.

Example: Web Application Module

Directory structure:

text
modules/
├── vpc/
│   ├── main.tf
│   ├── variables.tf
│   └── outputs.tf
├── security-groups/
│   ├── main.tf
│   ├── variables.tf
│   └── outputs.tf
├── ec2-instance/
│   ├── main.tf
│   ├── variables.tf
│   └── outputs.tf
└── web-app/                  ← Composed module
    ├── main.tf
    ├── variables.tf
    └── outputs.tf

modules/web-app/main.tf:

hcl
# modules/web-app/main.tf
locals {
  name = "${var.app_name}-${var.environment}"
}

# VPC
module "vpc" {
  source = "../vpc"
  
  name = local.name
  cidr = var.vpc_cidr
  public_subnet_cidrs  = var.public_subnet_cidrs
  private_subnet_cidrs = var.private_subnet_cidrs
}

# Security Groups
module "security_groups" {
  source = "../security-groups"
  
  vpc_id = module.vpc.vpc_id
  web_port = var.web_port
}

# EC2 Instances
module "ec2" {
  source = "../ec2-instance"
  
  instance_count = var.instance_count
  instance_type  = var.instance_type
  subnet_ids     = module.vpc.public_subnet_ids
  security_group_ids = [module.security_groups.web_sg_id]
  user_data      = var.user_data
}

# Outputs
output "load_balancer_dns" {
  value = module.alb.dns_name
}

output "instance_ips" {
  value = module.ec2.public_ips
}

Part 5: Module Versioning

Semantic Versioning for Modules

Use semantic versioning (SemVer) for modules:

VersionWhenExample
MAJORBreaking changesv2.0.0
MINORNew features, backward compatiblev1.3.0
PATCHBug fixes, backward compatiblev1.2.1

Publishing Modules

1. Create a Git repository:

text
terraform-aws-s3-bucket/
├── main.tf
├── variables.tf
├── outputs.tf
├── README.md
└── versions.tf

2. Tag your release:

bash
git tag -a v1.0.0 -m "Initial release"
git push origin v1.0.0

3. Use the module with version pinning:

hcl
module "s3_bucket" {
  source  = "git::https://github.com/company/terraform-aws-s3-bucket.git?ref=v1.0.0"
  
  bucket_name = "my-bucket"
  environment = "prod"
}

Version Constraints

hcl
# Exact version
version = "1.2.0"

# Patch updates only
version = "~> 1.2.0"  # 1.2.x, not 1.3.0

# Minor updates only
version = "~> 1.2"    # 1.x, not 2.0.0

# Greater than or equal
version = ">= 1.2.0"

# Range
version = ">= 1.2.0, < 2.0.0"

Part 6: Module Sources

Source Types

Source TypeExampleUse Case
Local./modules/vpcDevelopment, simple sharing
Gitgit::https://github.com/company/module.gitVersioned sharing
Terraform Registryterraform-aws-modules/vpc/awsPublic modules
HTTPhttps://example.com/module.zipDirect download

Module Source Best Practices

  • ✅ Use local modules for development

  • ✅ Use versioned Git modules for team sharing

  • ✅ Use the Terraform Registry for public modules

  • ✅ Pin module versions with ?ref=v1.0.0

  • ✅ Document module source locations


Part 7: Module Best Practices

Do's

  • Keep modules focused: One module, one responsibility

  • Use descriptive variable names: vpc_cidr not cidr

  • Add descriptions: Every variable and output needs a description

  • Use validation: Validate input values

  • Set sensible defaults: Make optional variables optional

  • Document your modules: README with examples

  • Version your modules: Semantic versioning

  • Use locals for complex expressions: Keep main.tf clean

Don'ts

  • Don't create modules for single resources: Not worth it

  • Don't hardcode provider configurations: Pass providers

  • Don't include secrets: Use variables or secrets managers

  • Don't create giant modules: Break into smaller modules

  • Don't skip testing: Test your modules


Part 8: Module Testing

Static Testing

bash
# Check syntax
terraform validate

# Check formatting
terraform fmt -check

# Security scanning
tfsec .
checkov -d .

Integration Testing

hcl
# test/main.tf
module "test_bucket" {
  source = "../modules/s3-bucket"
  
  bucket_name = "test-bucket"
  environment = "test"
}

output "bucket_created" {
  value = module.test_bucket.bucket_id != "" ? "Success" : "Failed"
}
bash
cd test
terraform init
terraform apply -auto-approve
# Verify resources exist
terraform destroy -auto-approve

Module Cheat Sheet

hcl
# Module structure
modules/
└── service/
    ├── main.tf
    ├── variables.tf
    ├── outputs.tf
    └── README.md

# Module call
module "name" {
  source = "./modules/service"
  
  var1 = "value1"
  var2 = var.some_value
}

# Reference outputs
output "result" {
  value = module.name.output_name
}

# Versioned module call
module "name" {
  source  = "git::https://github.com/org/repo.git?ref=v1.0.0"
  
  var1 = "value1"
}

Summary

AspectKey Points
WhatReusable infrastructure components
WhyDRY code, consistency, team sharing
Structuremain.tf, variables.tf, outputs.tf
SourcesLocal, Git, Registry
VersioningSemantic versioning
Best forVPCs, databases, web apps, common patterns

Modules are the foundation of scalable Terraform usage. Start with local modules, then publish to a registry as you mature.


Learn More

Practice Terraform modules with hands-on exercises in our interactive labs:
https://devops.trainwithsky.com/

Comments

Popular posts from this blog

🌐 Holographic Communications & 6G: The Future of Immersive Connectivity

  🌐 Holographic Communications & 6G: The Future of Immersive Connectivity 🚀 Introduction As the world moves towards 6G , a revolutionary technology is set to redefine digital interactions: Holographic Communications . Imagine real-time, 3D holographic video calls, immersive remote collaboration, and lifelike virtual experiences —all powered by ultra-fast, ultra-low-latency 6G networks . This topic explores Holographic Communications , its impact on various industries, key enabling technologies, and how 6G will bring this futuristic concept to reality . Shape Your Future with AI & Infinite Knowledge...!! Want to Generate Text-to-Voice, Images & Videos? http://www.ai.skyinfinitetech.com Read In-Depth Tech & Self-Improvement Blogs http://www.skyinfinitetech.com Watch Life-Changing Videos on YouTube https://www.youtube.com/@SkyInfinite-Learning Transform Your Skills, Business & Productivity – Join Us Today! 🔍 1. What is Holographic Communication? Hologr...

How to Use SKY TTS: The Complete, Step-by-Step Guide for 2025

 What is SKY TTS? SKY TTS  is a free, next-generation  AI audio creation platform  that brings together high-quality  Text-to-Speech ,  Speech-to-Text , and a full suite of professional  audio editing tools  in one seamless experience. Our vision is simple — to make advanced audio technology  free, accessible, and effortless  for everyone. From creators and educators to podcasters, developers, and businesses, SKY TTS helps users produce  studio-grade voice content  without expensive software or technical skills. With support for  70+ languages, natural voices, audio enhancement, waveform generation, and batch automation , SKY TTS has become a trusted all-in-one toolkit for modern digital audio workflows. Why Choose SKY TTS? Instant Conversion:  Enjoy rapid text-to-speech generation, even with large documents. Advanced Voice Settings:   Adjust speed, pitch, and style for a personalized listening experience. Multi-...

📊 Monitoring & Logging in Kubernetes – Tools like Prometheus, Grafana, and Fluentd

  Monitoring & Logging in Kubernetes – Tools like Prometheus, Grafana, and Fluentd Monitoring and logging are essential for maintaining a healthy and well-performing Kubernetes cluster. In this guide, we’ll cover why monitoring is important, key monitoring tools like Prometheus and Grafana, and logging tools like Fluentd to help you gain visibility into your cluster’s performance and logs. Shape Your Future with AI & Infinite Knowledge...!! Want to Generate Text-to-Voice, Images & Videos? http://www.ai.skyinfinitetech.com Read In-Depth Tech & Self-Improvement Blogs http://www.skyinfinitetech.com Watch Life-Changing Videos on YouTube https://www.youtube.com/@SkyInfinite-Learning Transform Your Skills, Business & Productivity – Join Us Today! 🚀 Introduction In today’s fast-paced cloud-native environment, Kubernetes has emerged as the de-facto container orchestration platform. But deploying and managing applications in Kubernetes is just half the ba...