Learn to create reusable, composable infrastructure components using Terraform modules. Master module design patterns, inputs/outputs, and best practices for production use.
Terraform modules are containers for multiple resources that are used together. Modules enable you to package and reuse Terraform configurations, promoting DRY (Don't Repeat Yourself) principles and making your infrastructure code more maintainable. In this lesson, we'll explore how to create, use, and publish modules effectively.
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, which consists of the resources defined in the .tf files in the main working directory.
Key Concepts:
Module Structure:
1module/
2├── main.tf # Primary resource definitions
3├── variables.tf # Input variable declarations
4├── outputs.tf # Output value declarations
5├── README.md # Documentation
6├── versions.tf # Terraform and provider version constraints
7└── examples/ # Usage examples
8 └── complete/
9 ├── main.tf
10 └── README.mdWhy Use Modules?
Module Types:
Local Modules:
1module "vpc" {
2 source = "./modules/vpc"
3}Registry Modules:
1module "vpc" {
2 source = "terraform-aws-modules/vpc/aws"
3 version = "5.0.0"
4}Git Modules:
1module "vpc" {
2 source = "git::https://github.com/company/terraform-modules.git//vpc?ref=v1.0.0"
3}Module Lifecycle:
Let's create a simple VPC module to understand module structure.
Step 1: Module Directory Structure
1mkdir -p modules/vpc
2cd modules/vpc
3touch main.tf variables.tf outputs.tf README.mdStep 2: Define Variables (variables.tf)
1variable "vpc_name" {
2 description = "Name of the VPC"
3 type = string
4}
5
6variable "vpc_cidr" {
7 description = "CIDR block for VPC"
8 type = string
9 default = "10.0.0.0/16"
10}
11
12variable "availability_zones" {
13 description = "List of availability zones"
14 type = list(string)
15}
16
17variable "public_subnet_cidrs" {
18 description = "CIDR blocks for public subnets"
19 type = list(string)
20}
21
22variable "private_subnet_cidrs" {
23 description = "CIDR blocks for private subnets"
24 type = list(string)
25}
26
27variable "enable_nat_gateway" {
28 description = "Enable NAT gateway for private subnets"
29 type = bool
30 default = true
31}
32
33variable "tags" {
34 description = "Tags to apply to all resources"
35 type = map(string)
36 default = {}
37}Step 3: Create Resources (main.tf)
1# VPC
2resource "aws_vpc" "this" {
3 cidr_block = var.vpc_cidr
4 enable_dns_hostnames = true
5 enable_dns_support = true
6
7 tags = merge(
8 var.tags,
9 {
10 Name = var.vpc_name
11 }
12 )
13}
14
15# Internet Gateway
16resource "aws_internet_gateway" "this" {
17 vpc_id = aws_vpc.this.id
18
19 tags = merge(
20 var.tags,
21 {
22 Name = "${var.vpc_name}-igw"
23 }
24 )
25}
26
27# Public Subnets
28resource "aws_subnet" "public" {
29 count = length(var.public_subnet_cidrs)
30
31 vpc_id = aws_vpc.this.id
32 cidr_block = var.public_subnet_cidrs[count.index]
33 availability_zone = var.availability_zones[count.index]
34 map_public_ip_on_launch = true
35
36 tags = merge(
37 var.tags,
38 {
39 Name = "${var.vpc_name}-public-${var.availability_zones[count.index]}"
40 Type = "public"
41 }
42 )
43}
44
45# Private Subnets
46resource "aws_subnet" "private" {
47 count = length(var.private_subnet_cidrs)
48
49 vpc_id = aws_vpc.this.id
50 cidr_block = var.private_subnet_cidrs[count.index]
51 availability_zone = var.availability_zones[count.index]
52
53 tags = merge(
54 var.tags,
55 {
56 Name = "${var.vpc_name}-private-${var.availability_zones[count.index]}"
57 Type = "private"
58 }
59 )
60}
61
62# NAT Gateway (optional)
63resource "aws_eip" "nat" {
64 count = var.enable_nat_gateway ? 1 : 0
65 domain = "vpc"
66
67 tags = merge(
68 var.tags,
69 {
70 Name = "${var.vpc_name}-nat-eip"
71 }
72 )
73}
74
75resource "aws_nat_gateway" "this" {
76 count = var.enable_nat_gateway ? 1 : 0
77
78 allocation_id = aws_eip.nat[0].id
79 subnet_id = aws_subnet.public[0].id
80
81 tags = merge(
82 var.tags,
83 {
84 Name = "${var.vpc_name}-nat"
85 }
86 )
87}Step 4: Define Outputs (outputs.tf)
1output "vpc_id" {
2 description = "ID of the VPC"
3 value = aws_vpc.this.id
4}
5
6output "vpc_cidr" {
7 description = "CIDR block of the VPC"
8 value = aws_vpc.this.cidr_block
9}
10
11output "public_subnet_ids" {
12 description = "IDs of public subnets"
13 value = aws_subnet.public[*].id
14}
15
16output "private_subnet_ids" {
17 description = "IDs of private subnets"
18 value = aws_subnet.private[*].id
19}
20
21output "nat_gateway_id" {
22 description = "ID of the NAT gateway"
23 value = var.enable_nat_gateway ? aws_nat_gateway.this[0].id : null
24}Step 5: Use the Module
1# Root module (main.tf)
2module "vpc" {
3 source = "./modules/vpc"
4
5 vpc_name = "production-vpc"
6 vpc_cidr = "10.0.0.0/16"
7 availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"]
8 public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
9 private_subnet_cidrs = ["10.0.11.0/24", "10.0.12.0/24", "10.0.13.0/24"]
10 enable_nat_gateway = true
11
12 tags = {
13 Environment = "production"
14 ManagedBy = "Terraform"
15 }
16}
17
18# Use module outputs
19output "vpc_id" {
20 value = module.vpc.vpc_id
21}Terraform supports multiple module sources, each with different versioning approaches.
Local Modules:
1module "vpc" {
2 source = "./modules/vpc" # Relative path
3 source = "../shared/vpc" # Parent directory
4 source = "/absolute/path/modules/vpc" # Absolute path
5}*No versioning - uses current files*
Terraform Registry:
1module "vpc" {
2 source = "terraform-aws-modules/vpc/aws"
3 version = "5.0.0" # Exact version
4
5 # Version constraints
6 version = ">= 5.0.0" # Greater than or equal
7 version = "~> 5.0" # Any 5.x version
8 version = ">= 5.0.0, < 6.0.0" # Range
9}GitHub/Git:
1# HTTPS
2module "vpc" {
3 source = "git::https://github.com/company/terraform-modules.git//vpc?ref=v1.0.0"
4}
5
6# SSH
7module "vpc" {
8 source = "git::ssh://[email protected]/company/terraform-modules.git//vpc?ref=v1.0.0"
9}
10
11# Branch
12module "vpc" {
13 source = "git::https://github.com/company/terraform-modules.git//vpc?ref=develop"
14}
15
16# Commit SHA
17module "vpc" {
18 source = "git::https://github.com/company/terraform-modules.git//vpc?ref=abc123def456"
19}HTTP/HTTPS URLs:
1module "vpc" {
2 source = "https://example.com/vpc-module.zip"
3}S3 Bucket:
1module "vpc" {
2 source = "s3::https://s3.amazonaws.com/my-bucket/vpc-module.zip"
3}Version Constraint Operators:
| Operator | Meaning | Example |
|----------|---------|---------|
| = | Exact version | = 1.2.3 |
| != | Not equal | != 1.2.3 |
| >, >=, <, <= | Comparison | >= 1.2.0 |
| ~> | Pessimistic constraint | ~> 1.2 (allows 1.3, 1.4, but not 2.0) |
Best Practices:
~> for automatic patch updatesUnderstanding how data flows into and out of modules is crucial.
Input Variables (variables.tf):
Required Variables:
1variable "vpc_name" {
2 description = "Name of the VPC"
3 type = string
4 # No default = required
5}Optional Variables with Defaults:
1variable "vpc_cidr" {
2 description = "CIDR block for VPC"
3 type = string
4 default = "10.0.0.0/16"
5}Complex Types:
1variable "subnets" {
2 description = "Map of subnet configurations"
3 type = map(object({
4 cidr_block = string
5 availability_zone = string
6 public = bool
7 }))
8 default = {}
9}
10
11variable "tags" {
12 description = "Resource tags"
13 type = map(string)
14 default = {}
15}Validation:
1variable "environment" {
2 description = "Environment name"
3 type = string
4
5 validation {
6 condition = contains(["dev", "staging", "prod"], var.environment)
7 error_message = "Environment must be dev, staging, or prod."
8 }
9}
10
11variable "vpc_cidr" {
12 description = "VPC CIDR block"
13 type = string
14
15 validation {
16 condition = can(cidrhost(var.vpc_cidr, 0))
17 error_message = "Must be a valid IPv4 CIDR block."
18 }
19}Output Values (outputs.tf):
Simple Outputs:
1output "vpc_id" {
2 description = "ID of the VPC"
3 value = aws_vpc.this.id
4}Sensitive Outputs:
1output "database_password" {
2 description = "Database master password"
3 value = aws_db_instance.this.password
4 sensitive = true # Hide from console output
5}Complex Outputs:
1output "subnet_info" {
2 description = "Subnet information"
3 value = {
4 public_subnet_ids = aws_subnet.public[*].id
5 private_subnet_ids = aws_subnet.private[*].id
6 subnet_cidrs = {
7 public = aws_subnet.public[*].cidr_block
8 private = aws_subnet.private[*].cidr_block
9 }
10 }
11}Conditional Outputs:
1output "nat_gateway_id" {
2 description = "NAT gateway ID (if enabled)"
3 value = var.enable_nat_gateway ? aws_nat_gateway.this[0].id : null
4}Using Module Outputs:
1# Call module
2module "vpc" {
3 source = "./modules/vpc"
4 # ... inputs
5}
6
7# Use outputs in resources
8resource "aws_instance" "web" {
9 subnet_id = module.vpc.public_subnet_ids[0]
10 vpc_security_group_ids = [
11 aws_security_group.web.id
12 ]
13}
14
15# Use outputs in other modules
16module "database" {
17 source = "./modules/database"
18
19 vpc_id = module.vpc.vpc_id
20 subnet_ids = module.vpc.private_subnet_ids
21}
22
23# Pass outputs to root outputs
24output "vpc_id" {
25 value = module.vpc.vpc_id
26}Build complex infrastructure by composing multiple modules.
Pattern 1: Layered Architecture
1# Layer 1: Network
2module "vpc" {
3 source = "./modules/vpc"
4 # ... config
5}
6
7# Layer 2: Security
8module "security_groups" {
9 source = "./modules/security-groups"
10 vpc_id = module.vpc.vpc_id
11}
12
13# Layer 3: Compute
14module "application" {
15 source = "./modules/application"
16
17 vpc_id = module.vpc.vpc_id
18 subnet_ids = module.vpc.private_subnet_ids
19 security_group = module.security_groups.app_sg_id
20}
21
22# Layer 4: Data
23module "database" {
24 source = "./modules/database"
25
26 vpc_id = module.vpc.vpc_id
27 subnet_ids = module.vpc.private_subnet_ids
28 security_group = module.security_groups.db_sg_id
29}Pattern 2: Environment-Specific Composition
1# environments/production/main.tf
2module "vpc" {
3 source = "../../modules/vpc"
4
5 vpc_name = "prod-vpc"
6 vpc_cidr = "10.0.0.0/16"
7 enable_nat_gateway = true
8 enable_vpn_gateway = true
9}
10
11module "eks" {
12 source = "../../modules/eks"
13
14 cluster_name = "prod-cluster"
15 cluster_version = "1.28"
16
17 vpc_id = module.vpc.vpc_id
18 subnet_ids = module.vpc.private_subnet_ids
19
20 node_groups = {
21 general = {
22 instance_types = ["t3.large"]
23 min_size = 3
24 max_size = 10
25 desired_size = 5
26 }
27 }
28}
29
30# environments/development/main.tf
31module "vpc" {
32 source = "../../modules/vpc"
33
34 vpc_name = "dev-vpc"
35 vpc_cidr = "10.1.0.0/16"
36 enable_nat_gateway = false # Cost savings
37 enable_vpn_gateway = false
38}
39
40module "eks" {
41 source = "../../modules/eks"
42
43 cluster_name = "dev-cluster"
44 cluster_version = "1.28"
45
46 vpc_id = module.vpc.vpc_id
47 subnet_ids = module.vpc.private_subnet_ids
48
49 node_groups = {
50 general = {
51 instance_types = ["t3.small"]
52 min_size = 1
53 max_size = 3
54 desired_size = 1
55 }
56 }
57}Pattern 3: Wrapper Modules
1# modules/application-stack/main.tf
2# Wrapper module that combines multiple modules
3
4module "vpc" {
5 source = "../vpc"
6
7 vpc_name = var.stack_name
8 # ... other vpc config
9}
10
11module "alb" {
12 source = "../alb"
13
14 name = "${var.stack_name}-alb"
15 vpc_id = module.vpc.vpc_id
16 subnet_ids = module.vpc.public_subnet_ids
17}
18
19module "ecs_cluster" {
20 source = "../ecs"
21
22 cluster_name = var.stack_name
23 vpc_id = module.vpc.vpc_id
24 subnet_ids = module.vpc.private_subnet_ids
25}
26
27module "rds" {
28 source = "../rds"
29
30 identifier = "${var.stack_name}-db"
31 vpc_id = module.vpc.vpc_id
32 subnet_ids = module.vpc.private_subnet_ids
33}
34
35# Simplified interface
36variable "stack_name" {
37 type = string
38}
39
40variable "environment" {
41 type = string
42}
43
44# Expose essential outputs
45output "alb_dns_name" {
46 value = module.alb.dns_name
47}
48
49output "database_endpoint" {
50 value = module.rds.endpoint
51 sensitive = true
52}Pattern 4: For_Each with Modules
1# Create multiple VPCs
2variable "vpcs" {
3 type = map(object({
4 cidr_block = string
5 region = string
6 }))
7}
8
9module "vpcs" {
10 for_each = var.vpcs
11 source = "./modules/vpc"
12
13 vpc_name = each.key
14 vpc_cidr = each.value.cidr_block
15
16 providers = {
17 aws = aws.${each.value.region}
18 }
19}
20
21# Reference specific VPC
22output "production_vpc_id" {
23 value = module.vpcs["production"].vpc_id
24}Share your modules with the community or your organization.
Prerequisites:
terraform-<PROVIDER>-<NAME>terraform-aws-vpcModule Structure for Registry:
1terraform-aws-vpc/
2├── main.tf
3├── variables.tf
4├── outputs.tf
5├── README.md
6├── LICENSE
7├── .gitignore
8├── versions.tf
9├── examples/
10│ ├── complete/
11│ │ ├── main.tf
12│ │ ├── variables.tf
13│ │ ├── outputs.tf
14│ │ └── README.md
15│ └── simple/
16│ └── main.tf
17└── modules/ # Optional: submodules
18 └── subnets/
19 ├── main.tf
20 ├── variables.tf
21 └── outputs.tfversions.tf:
1terraform {
2 required_version = ">= 1.0"
3
4 required_providers {
5 aws = {
6 source = "hashicorp/aws"
7 version = ">= 5.0"
8 }
9 }
10}README.md Structure:
1# AWS VPC Terraform Module
2
3Terraform module which creates VPC resources on AWS.
4
5## Usage
6
7\`\`\`hcl
8module "vpc" {
9 source = "terraform-aws-modules/vpc/aws"
10 version = "5.0.0"
11
12 name = "my-vpc"
13 cidr = "10.0.0.0/16"
14
15 azs = ["us-east-1a", "us-east-1b"]
16 private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
17 public_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
18}
19\`\`\`
20
21## Examples
22
23- [Complete VPC](examples/complete)
24- [Simple VPC](examples/simple)
25
26## Requirements
27
28| Name | Version |
29|------|---------|
30| terraform | >= 1.0 |
31| aws | >= 5.0 |
32
33## Inputs
34
35| Name | Description | Type | Default | Required |
36|------|-------------|------|---------|:--------:|
37| name | Name of VPC | \`string\` | n/a | yes |
38| cidr | CIDR block | \`string\` | n/a | yes |
39
40## Outputs
41
42| Name | Description |
43|------|-------------|
44| vpc_id | ID of the VPC |
45| vpc_cidr | CIDR block |Publishing Steps:
1. Create GitHub Repository
1git init
2git add .
3git commit -m "Initial commit"
4git remote add origin https://github.com/username/terraform-aws-vpc.git
5git push -u origin main2. Create Release with Tag
1git tag -a v1.0.0 -m "First release"
2git push origin v1.0.03. Publish to Terraform Registry
4. Using Your Published Module
1module "vpc" {
2 source = "username/vpc/aws"
3 version = "1.0.0"
4
5 # ... configuration
6}Versioning Best Practices:
vMAJOR.MINOR.PATCHPrivate Module Registry:
For Terraform Cloud/Enterprise:
1module "vpc" {
2 source = "app.terraform.io/my-org/vpc/aws"
3 version = "1.0.0"
4}Testing ensures modules work correctly and don't break with changes.
Testing Approaches:
1. Terraform Validate
1cd modules/vpc
2terraform init
3terraform validate2. Example-Based Testing
1cd examples/complete
2terraform init
3terraform plan
4terraform apply -auto-approve
5# Test functionality
6terraform destroy -auto-approve3. Terratest (Go-based)
1// test/vpc_test.go
2package test
3
4import (
5 "testing"
6 "github.com/gruntwork-io/terratest/modules/terraform"
7 "github.com/stretchr/testify/assert"
8)
9
10func TestVPCModule(t *testing.T) {
11 terraformOptions := &terraform.Options{
12 TerraformDir: "../examples/complete",
13 Vars: map[string]interface{}{
14 "vpc_name": "test-vpc",
15 },
16 }
17
18 defer terraform.Destroy(t, terraformOptions)
19 terraform.InitAndApply(t, terraformOptions)
20
21 vpcID := terraform.Output(t, terraformOptions, "vpc_id")
22 assert.NotEmpty(t, vpcID)
23}4. Kitchen-Terraform
1# .kitchen.yml
2driver:
3 name: terraform
4
5provisioner:
6 name: terraform
7
8platforms:
9 - name: aws
10
11suites:
12 - name: default
13 driver:
14 root_module_directory: test/fixtures/default5. Checkov (Security/Compliance)
1checkov --directory modules/vpc6. TFLint (Linting)
1cd modules/vpc
2tflintCI/CD Pipeline Example (GitHub Actions):
1name: Module Tests
2
3on: [push, pull_request]
4
5jobs:
6 test:
7 runs-on: ubuntu-latest
8 steps:
9 - uses: actions/checkout@v3
10
11 - name: Setup Terraform
12 uses: hashicorp/setup-terraform@v2
13 with:
14 terraform_version: 1.5.0
15
16 - name: Terraform Format Check
17 run: terraform fmt -check -recursive
18
19 - name: Terraform Init
20 run: terraform init
21 working-directory: ./modules/vpc
22
23 - name: Terraform Validate
24 run: terraform validate
25 working-directory: ./modules/vpc
26
27 - name: TFLint
28 run: |
29 curl -s https://raw.githubusercontent.com/terraform-linters/tflint/master/install_linux.sh | bash
30 tflint
31 working-directory: ./modules/vpc
32
33 - name: Checkov Security Scan
34 run: |
35 pip install checkov
36 checkov -d modules/vpcFollow these practices for production-ready modules.
1. Module Design Principles:
Single Responsibility:
1# Good: VPC module creates only VPC resources
2module "vpc" {
3 source = "./modules/vpc"
4}
5
6# Bad: VPC module creates VPC + EC2 + RDS
7# Split into separate modules insteadSensible Defaults:
1variable "enable_nat_gateway" {
2 description = "Enable NAT gateway for private subnets"
3 type = bool
4 default = true # Sensible default for most cases
5}Composition Over Inheritance:
1# Compose modules, don't nest deeply
2module "vpc" {
3 source = "./modules/vpc"
4}
5
6module "app" {
7 source = "./modules/application"
8 vpc_id = module.vpc.vpc_id # Explicit dependency
9}2. Documentation Standards:
Comprehensive README:
Inline Comments:
1# Create NAT gateway for private subnet internet access
2# Note: Incurs hourly charges (~$32/month per NAT gateway)
3resource "aws_nat_gateway" "this" {
4 count = var.enable_nat_gateway ? 1 : 0
5 # ...
6}Variable Descriptions:
1variable "vpc_cidr" {
2 description = "CIDR block for VPC. Must be valid IPv4 CIDR (e.g., 10.0.0.0/16)"
3 type = string
4}3. Input Validation:
1variable "environment" {
2 type = string
3 validation {
4 condition = contains(["dev", "staging", "prod"], var.environment)
5 error_message = "Environment must be dev, staging, or prod."
6 }
7}
8
9variable "vpc_cidr" {
10 type = string
11 validation {
12 condition = can(cidrhost(var.vpc_cidr, 0))
13 error_message = "Must be a valid IPv4 CIDR block."
14 }
15}4. Output Organization:
1# Group related outputs
2output "vpc" {
3 description = "VPC attributes"
4 value = {
5 id = aws_vpc.this.id
6 cidr_block = aws_vpc.this.cidr_block
7 arn = aws_vpc.this.arn
8 }
9}
10
11output "subnets" {
12 description = "Subnet information"
13 value = {
14 public_ids = aws_subnet.public[*].id
15 private_ids = aws_subnet.private[*].id
16 }
17}5. Version Constraints:
1# versions.tf
2terraform {
3 required_version = ">= 1.0"
4
5 required_providers {
6 aws = {
7 source = "hashicorp/aws"
8 version = ">= 5.0, < 6.0"
9 }
10 }
11}6. Resource Naming:
1# Use consistent naming conventions
2resource "aws_vpc" "this" { # Use "this" for single instances
3 # ...
4}
5
6resource "aws_subnet" "public" { # Use descriptive names for multiple
7 count = length(var.public_subnet_cidrs)
8 # ...
9}7. Tag Management:
1variable "tags" {
2 description = "Common tags to apply to all resources"
3 type = map(string)
4 default = {}
5}
6
7resource "aws_vpc" "this" {
8 # Merge module tags with user-provided tags
9 tags = merge(
10 {
11 Name = var.vpc_name
12 ManagedBy = "Terraform"
13 },
14 var.tags
15 )
16}8. Security Considerations:
9. Performance Optimization:
for_each instead of count when possible10. Module Versioning:
A comprehensive example showing module composition for a production-ready three-tier application with VPC, load balancer, application servers, and database.
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Terraform Module Architecture</title>
7 <style>
8 body {
9 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
10 line-height: 1.6;
11 max-width: 1000px;
12 margin: 0 auto;
13 padding: 20px;
14 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
15 }
16 .container {
17 background: white;
18 padding: 30px;
19 border-radius: 10px;
20 box-shadow: 0 10px 30px rgba(0,0,0,0.3);
21 }
22 h1 { color: #5e35b1; margin-top: 0; }
23 h2 { color: #7e57c2; border-bottom: 2px solid #e0e0e0; padding-bottom: 10px; margin-top: 30px; }
24 h3 { color: #9575cd; }
25 .architecture {
26 background: #f5f5f5;
27 padding: 20px;
28 border-radius: 8px;
29 margin: 20px 0;
30 border-left: 4px solid #5e35b1;
31 }
32 .module-box {
33 background: white;
34 border: 2px solid #5e35b1;
35 border-radius: 8px;
36 padding: 15px;
37 margin: 10px 0;
38 }
39 .module-box h4 {
40 margin-top: 0;
41 color: #5e35b1;
42 }
43 .code-block {
44 background: #263238;
45 color: #aed581;
46 padding: 15px;
47 border-radius: 5px;
48 overflow-x: auto;
49 margin: 10px 0;
50 font-family: 'Courier New', monospace;
51 font-size: 14px;
52 }
53 .flow-diagram {
54 background: linear-gradient(90deg, #e3f2fd 0%, #f3e5f5 100%);
55 padding: 20px;
56 border-radius: 8px;
57 margin: 20px 0;
58 }
59 .tier {
60 background: white;
61 border-left: 5px solid #7e57c2;
62 padding: 15px;
63 margin: 10px 0;
64 border-radius: 4px;
65 }
66 .benefits {
67 background: #e8f5e9;
68 padding: 15px;
69 border-radius: 8px;
70 margin: 15px 0;
71 }
72 .benefits ul { margin: 10px 0; }
73 .warning {
74 background: #fff3e0;
75 border-left: 4px solid #ff9800;
76 padding: 15px;
77 margin: 15px 0;
78 border-radius: 4px;
79 }
80 table {
81 width: 100%;
82 border-collapse: collapse;
83 margin: 15px 0;
84 }
85 th, td {
86 padding: 12px;
87 text-align: left;
88 border-bottom: 1px solid #e0e0e0;
89 }
90 th {
91 background: #f5f5f5;
92 font-weight: 600;
93 color: #5e35b1;
94 }
95 .tag {
96 display: inline-block;
97 background: #e1bee7;
98 color: #6a1b9a;
99 padding: 3px 8px;
100 border-radius: 3px;
101 font-size: 12px;
102 margin: 2px;
103 }
104 </style>
105</head>
106<body>
107 <div class="container">
108 <h1>🏗️ Terraform Module Architecture</h1>
109 <p><strong>Building Production-Ready Infrastructure with Composable Modules</strong></p>
110
111 <div class="benefits">
112 <strong>Why Use Terraform Modules?</strong>
113 <ul>
114 <li><strong>Reusability:</strong> Write once, use everywhere</li>
115 <li><strong>Consistency:</strong> Enforce standards across teams</li>
116 <li><strong>Abstraction:</strong> Hide complexity, expose simplicity</li>
117 <li><strong>Testing:</strong> Test components in isolation</li>
118 <li><strong>Versioning:</strong> Manage changes systematically</li>
119 <li><strong>Collaboration:</strong> Share best practices</li>
120 </ul>
121 </div>
122
123 <h2>Three-Tier Application Architecture</h2>
124
125 <div class="flow-diagram">
126 <h3>Architecture Overview</h3>
127 <div class="tier">
128 <strong>🌐 Tier 1: Presentation (Public)</strong><br>
129 <span class="tag">Load Balancer</span>
130 <span class="tag">Public Subnets</span>
131 <span class="tag">Internet Gateway</span>
132 <p>Handles incoming HTTP/HTTPS traffic from the internet</p>
133 </div>
134 <div style="text-align: center; font-size: 24px; color: #7e57c2;">↓</div>
135 <div class="tier">
136 <strong>⚙️ Tier 2: Application (Private)</strong><br>
137 <span class="tag">EC2 Instances</span>
138 <span class="tag">Auto Scaling</span>
139 <span class="tag">Private Subnets</span>
140 <span class="tag">NAT Gateway</span>
141 <p>Application servers processing business logic</p>
142 </div>
143 <div style="text-align: center; font-size: 24px; color: #7e57c2;">↓</div>
144 <div class="tier">
145 <strong>🗄️ Tier 3: Data (Private)</strong><br>
146 <span class="tag">RDS Database</span>
147 <span class="tag">Private Subnets</span>
148 <span class="tag">Multi-AZ</span>
149 <p>Database storing application data securely</p>
150 </div>
151 </div>
152
153 <h2>Module Structure</h2>
154
155 <div class="architecture">
156 <h3>Project Layout</h3>
157 <div class="code-block">
158infrastructure/<br>
159├── modules/<br>
160│ ├── vpc/ # Network foundation<br>
161│ ├── alb/ # Application Load Balancer<br>
162│ ├── compute/ # EC2 Auto Scaling Group<br>
163│ ├── database/ # RDS PostgreSQL<br>
164│ └── security-groups/ # Firewall rules<br>
165├── environments/<br>
166│ ├── production/<br>
167│ │ ├── main.tf<br>
168│ │ ├── variables.tf<br>
169│ │ └── outputs.tf<br>
170│ └── staging/<br>
171│ └── main.tf<br>
172└── README.md
173 </div>
174 </div>
175
176 <h2>Module Composition Example</h2>
177
178 <div class="module-box">
179 <h4>1. VPC Module (Network Foundation)</h4>
180 <p><strong>Purpose:</strong> Creates isolated network with public/private subnets</p>
181 <div class="code-block">
182module "vpc" {<br>
183 source = "../../modules/vpc"<br>
184<br>
185 vpc_name = "production-vpc"<br>
186 vpc_cidr = "10.0.0.0/16"<br>
187 availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"]<br>
188 public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]<br>
189 private_subnet_cidrs = ["10.0.11.0/24", "10.0.12.0/24", "10.0.13.0/24"]<br>
190 enable_nat_gateway = true<br>
191 single_nat_gateway = false # HA: one NAT per AZ<br>
192}<br>
193 </div>
194 <p><strong>Outputs:</strong> vpc_id, public_subnet_ids, private_subnet_ids</p>
195 </div>
196
197 <div class="module-box">
198 <h4>2. Security Groups Module</h4>
199 <p><strong>Purpose:</strong> Defines firewall rules for each tier</p>
200 <div class="code-block">
201module "security_groups" {<br>
202 source = "../../modules/security-groups"<br>
203<br>
204 vpc_id = module.vpc.vpc_id<br>
205 name_prefix = "prod"<br>
206<br>
207 # Allow HTTP/HTTPS from internet to ALB<br>
208 alb_ingress_cidrs = ["0.0.0.0/0"]<br>
209}<br>
210 </div>
211 <p><strong>Outputs:</strong> alb_sg_id, app_sg_id, db_sg_id</p>
212 </div>
213
214 <div class="module-box">
215 <h4>3. Application Load Balancer Module</h4>
216 <p><strong>Purpose:</strong> Distributes traffic across application servers</p>
217 <div class="code-block">
218module "alb" {<br>
219 source = "../../modules/alb"<br>
220<br>
221 name = "production-alb"<br>
222 vpc_id = module.vpc.vpc_id<br>
223 subnet_ids = module.vpc.public_subnet_ids<br>
224 security_groups = [module.security_groups.alb_sg_id]<br>
225<br>
226 enable_https = true<br>
227 certificate_arn = "arn:aws:acm:..."<br>
228}<br>
229 </div>
230 <p><strong>Outputs:</strong> alb_dns_name, target_group_arn</p>
231 </div>
232
233 <div class="module-box">
234 <h4>4. Compute Module (Application Servers)</h4>
235 <p><strong>Purpose:</strong> Auto-scaling application servers</p>
236 <div class="code-block">
237module "compute" {<br>
238 source = "../../modules/compute"<br>
239<br>
240 name = "production-app"<br>
241 vpc_id = module.vpc.vpc_id<br>
242 subnet_ids = module.vpc.private_subnet_ids<br>
243 security_group_ids = [module.security_groups.app_sg_id]<br>
244<br>
245 instance_type = "t3.large"<br>
246 min_size = 3<br>
247 max_size = 10<br>
248 desired_capacity = 5<br>
249<br>
250 target_group_arns = [module.alb.target_group_arn]<br>
251<br>
252 user_data = templatefile("${path.module}/user-data.sh", {<br>
253 database_endpoint = module.database.endpoint<br>
254 })<br>
255}<br>
256 </div>
257 <p><strong>Outputs:</strong> autoscaling_group_id, launch_template_id</p>
258 </div>
259
260 <div class="module-box">
261 <h4>5. Database Module (RDS)</h4>
262 <p><strong>Purpose:</strong> Managed PostgreSQL database</p>
263 <div class="code-block">
264module "database" {<br>
265 source = "../../modules/database"<br>
266<br>
267 identifier = "production-db"<br>
268 engine = "postgres"<br>
269 engine_version = "15.0"<br>
270 instance_class = "db.r5.xlarge"<br>
271<br>
272 vpc_id = module.vpc.vpc_id<br>
273 subnet_ids = module.vpc.private_subnet_ids<br>
274 security_group_ids = [module.security_groups.db_sg_id]<br>
275<br>
276 allocated_storage = 100<br>
277 multi_az = true<br>
278 backup_retention_period = 7<br>
279}<br>
280 </div>
281 <p><strong>Outputs:</strong> endpoint, port, database_name</p>
282 </div>
283
284 <h2>Dependency Flow</h2>
285
286 <table>
287 <tr>
288 <th>Module</th>
289 <th>Depends On</th>
290 <th>Reason</th>
291 </tr>
292 <tr>
293 <td>VPC</td>
294 <td><em>None</em></td>
295 <td>Foundation layer</td>
296 </tr>
297 <tr>
298 <td>Security Groups</td>
299 <td>VPC</td>
300 <td>Needs VPC ID</td>
301 </tr>
302 <tr>
303 <td>ALB</td>
304 <td>VPC, Security Groups</td>
305 <td>Needs subnets and security group</td>
306 </tr>
307 <tr>
308 <td>Compute</td>
309 <td>VPC, Security Groups, ALB, Database</td>
310 <td>Needs network, firewall rules, target group, DB endpoint</td>
311 </tr>
312 <tr>
313 <td>Database</td>
314 <td>VPC, Security Groups</td>
315 <td>Needs subnets and security group</td>
316 </tr>
317 </table>
318
319 <h2>Benefits of This Approach</h2>
320
321 <div class="benefits">
322 <strong>Modularity Benefits:</strong>
323 <ul>
324 <li><strong>Reusability:</strong> VPC module used in dev, staging, prod</li>
325 <li><strong>Testability:</strong> Test each module independently</li>
326 <li><strong>Maintainability:</strong> Update one module, affects all environments</li>
327 <li><strong>Scalability:</strong> Easy to add new tiers or components</li>
328 <li><strong>Team Collaboration:</strong> Different teams own different modules</li>
329 </ul>
330 </div>
331
332 <div class="warning">
333 <strong>⚠️ Important Considerations:</strong>
334 <ul>
335 <li><strong>Versioning:</strong> Pin module versions in production</li>
336 <li><strong>State Management:</strong> Use remote backend with locking</li>
337 <li><strong>Testing:</strong> Test module changes in lower environments first</li>
338 <li><strong>Documentation:</strong> Keep READMEs up to date</li>
339 <li><strong>Security:</strong> Never hardcode credentials</li>
340 </ul>
341 </div>
342
343 <h2>Environment-Specific Variations</h2>
344
345 <table>
346 <tr>
347 <th>Component</th>
348 <th>Development</th>
349 <th>Production</th>
350 </tr>
351 <tr>
352 <td>NAT Gateway</td>
353 <td>None (cost saving)</td>
354 <td>HA (one per AZ)</td>
355 </tr>
356 <tr>
357 <td>EC2 Instances</td>
358 <td>t3.micro (1-2)</td>
359 <td>t3.large (3-10)</td>
360 </tr>
361 <tr>
362 <td>RDS</td>
363 <td>Single AZ, no backups</td>
364 <td>Multi-AZ, daily backups</td>
365 </tr>
366 <tr>
367 <td>ALB</td>
368 <td>HTTP only</td>
369 <td>HTTPS with ACM cert</td>
370 </tr>
371 </table>
372
373 <h2>Next Steps</h2>
374 <ul>
375 <li>Create your first custom module</li>
376 <li>Use modules from Terraform Registry</li>
377 <li>Compose modules for your infrastructure</li>
378 <li>Implement module testing with Terratest</li>
379 <li>Publish modules to private registry</li>
380 <li>Learn about workspace-based deployment</li>
381 </ul>
382 </div>
383</body>
384</html>This example demonstrates professional module composition for a production three-tier application:
Architecture Layers:
Key Benefits:
This pattern is used by leading companies for production infrastructure.
Your progress is saved in this browser, on this device only.