Building Reusable Terraform Modules

April 10, 2026 Terraform IaC

As your infrastructure grows, copy-pasting Terraform code between projects becomes a maintenance nightmare. Modules solve this by encapsulating reusable infrastructure patterns with well-defined inputs and outputs.

Module Structure

A good module follows a predictable layout:

modules/s3-static-site/
├── main.tf          # Core resources
├── variables.tf     # Input variables
├── outputs.tf       # Output values
├── versions.tf      # Provider constraints
└── README.md        # Usage documentation

Designing the Interface

Think of your module's variables as its API. Provide sensible defaults where possible, add validation rules, and use descriptive names. Here's an example:

variable "bucket_name" {
  type        = string
  description = "Name of the S3 bucket"

  validation {
    condition     = length(var.bucket_name) <= 63
    error_message = "Bucket name must be 63 characters or fewer."
  }
}

variable "enable_versioning" {
  type        = bool
  description = "Enable S3 bucket versioning"
  default     = true
}

Composition Over Complexity

Resist the urge to build one mega-module that does everything. Instead, compose smaller focused modules together. A static site module shouldn't also manage DNS — let a separate Route 53 module handle that. This keeps each module testable and easy to reason about.

Versioning and Publishing

Tag your module releases with semantic versioning. When consuming modules, pin to a specific version to avoid surprises:

module "blog_site" {
  source  = "git::https://github.com/org/tf-modules.git//s3-static-site?ref=v1.2.0"
  
  bucket_name       = "my-blog"
  enable_versioning = true
}

Well-designed modules are an investment that pays off every time a new project needs the same infrastructure pattern.

← Back to all posts