SuperAPI AI Directory

terraform-module-patterns

Deployment & operations

View repository

Updated 10-21 05:28

About this skill

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