Terraform Module Patterns — Terraform Module Design Pattern Guide
Patterns and best practices for designing reusable and maintainable Terraform modules.
Directory Structure Patterns
Pattern 1: Module Layer Separation
infrastructure/
├── modules/ # Reusable modules
│ ├── networking/
│ │ ├── vpc/
│ │ │ ├── main.tf
│ │ │ ├── variables.tf
│ │ │ ├── outputs.tf
│ │ │ └── README.md
│ │ ├── security-group/
│ │ └── load-balancer/
│ ├── compute/
│ │ ├── ecs-service/
│ │ ├── lambda/
│ │ └── ec2-asg/
│ └── data/
│ ├── rds/
│ ├── elasticache/
│ └── s3/
├── environments/ # Per-environment configuration
│ ├── dev/
│ │ ├── main.tf # Module invocations
│ │ ├── terraform.tfvars # Environment variables
│ │ └── backend.tf # State storage
│ ├── staging/
│ └── prod/
└── global/ # Cross-environment (IAM, DNS)
├── iam/
└── route53/
Pattern 2: Terragrunt-based DRY
infrastructure/
├── modules/ # Same as above
├── terragrunt.hcl # Root config (backend, provider)
└── environments/
├── terragrunt.hcl # Common variables
├── dev/
│ ├── terragrunt.hcl # include root + env vars
│ ├── vpc/
│ │ └── terragrunt.hcl # module source + inputs
│ ├── ecs/
│ └── rds/
└── prod/
Module Design Principles
1. Single Responsibility Module
# Good: VPC module handles only VPC
module "vpc" {
source = "./modules/networking/vpc"
cidr_block = "10.0.0.0/16"
az_count = 3
}
# Bad: One module for all infrastructure
module "everything" { # Anti-pattern!
source = "./modules/full-stack"
}
2. Input/Output Design
# variables.tf — Validation required
variable "instance_type" {
type = string
description = "EC2 instance type"
default = "t3.medium"
validation {
condition = can(regex("^t3\\.", var.instance_type))
error_message = "Only t3 family is allowed."
}
}
variable "environment" {
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Must be one of dev, staging, prod."
}
}
# outputs.tf — Expose only values needed by other modules
output "vpc_id" {
value = aws_vpc.main.id
description = "ID of the created VPC"
}
3. Conditional Resources
variable "enable_monitoring" {
type = bool
default = true
}
resource "aws_cloudwatch_metric_alarm" "cpu" {
count = var.enable_monitoring ? 1 : 0
# ...
}
State Management Patterns
Remote State Configuration
# backend.tf
terraform {
backend "s3" {
bucket = "company-terraform-state"
key = "environments/prod/vpc/terraform.tfstate"
region = "ap-northeast-2"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
State Separation Strategies
| Strategy |
Separation Basis |
Pros |
Cons |
| Per-environment |
dev/staging/prod |
Environment isolation, independent deployment |
Need data source for cross-env references |
| Per-layer |
network/compute/data |
Reduced blast radius |
Complex reference management |
| Per-team |
Team-owned resources |
Autonomy |
Shared resource management difficulty |
| Per-lifecycle |
Change frequency |
Stable resource protection |
Boundary definition difficulty |
Cross-State References
# Read VPC ID from network state
data "terraform_remote_state" "network" {
backend = "s3"
config = {
bucket = "company-terraform-state"
key = "environments/prod/network/terraform.tfstate"
region = "ap-northeast-2"
}
}
# Usage
resource "aws_ecs_service" "app" {
network_configuration {
subnets = data.terraform_remote_state.network.outputs.private_subnet_ids
}
}
Tagging Strategy
locals {
common_tags = {
Environment = var.environment
Project = var.project_name
ManagedBy = "terraform"
Team = var.team_name
CostCenter = var.cost_center
}
}
resource "aws_instance" "app" {
tags = merge(local.common_tags, {
Name = "${var.project_name}-${var.environment}-app"
Role = "application"
})
}
Anti-patterns and Solutions
| Anti-pattern |
Problem |
Solution |
| Giant state file |
Slow plan/apply, large blast radius |
Split by layer |
| Hardcoded values |
Cannot reuse across environments |
variables + tfvars |
| Provider inside module |
Lost flexibility |
Provider only at root |
| Complex conditions with count |
Resource recreation on index changes |
Use for_each |
| Plaintext secrets |
Security violation |
SSM/Vault references |