CloudOpsGuide
terraform

Terraform Modules: Building Reusable Infrastructure

Intermediate
11 minutes
October 2026
CloudOpsGuide Team

Terraform Modules: Building Reusable Infrastructure

Once your Terraform configs pass a few hundred lines, copy-paste becomes the enemy. Modules are Terraform's answer: package a set of resources behind a clean interface of inputs and outputs, then reuse it everywhere.

Table of Contents

Module anatomy

A module is just a directory of .tf files:

modules/vpc/
  main.tf       # resources
  variables.tf  # inputs
  outputs.tf    # exports
# modules/vpc/variables.tf
variable "cidr_block" { type = string }
variable "environment" { type = string }

# modules/vpc/main.tf
resource "aws_vpc" "this" {
  cidr_block = var.cidr_block
  tags = { Environment = var.environment }
}

# modules/vpc/outputs.tf
output "vpc_id" { value = aws_vpc.this.id }

Calling the module

module "vpc_prod" {
  source      = "./modules/vpc"
  cidr_block  = "10.0.0.0/16"
  environment = "prod"
}

module "vpc_staging" {
  source      = "./modules/vpc"
  cidr_block  = "10.1.0.0/16"
  environment = "staging"
}

# consume outputs
resource "aws_subnet" "web" {
  vpc_id = module.vpc_prod.vpc_id
  # ...
}

Two environments, one implementation — drift between them becomes structurally impossible.

Sources: local, git, and registry

source = "./modules/vpc"                              # local
source = "git::https://github.com/org/tf-modules.git//vpc?ref=v1.2.0"
source = "terraform-aws-modules/vpc/aws"              # public registry
version = "~> 5.0"                                    # required for registry modules

The Terraform Registry hosts battle-tested community modules (the terraform-aws-modules/* family especially). Pin versions — ref=v1.2.0 or version — always.

Design rules for good modules

  • Small and opinionated. A module that "does everything" is a framework nobody can debug. One module = one concept (a VPC, a database, a service).
  • Sensible defaults. variable "enable_flow_logs" { default = true } makes the secure path the easy path.
  • Export only what's consumed. Outputs are your API contract; exporting internals invites coupling.
  • No provider blocks inside modules. The caller configures providers; modules declare required_providers versions only.
  • Version your modules. Git tags let callers upgrade deliberately instead of breaking on every push.

Iterating without pain

terraform init -upgrade     # refresh module sources
terraform plan -target=module.vpc_prod   # scope a plan
terraform state mv aws_vpc.old module.vpc_prod.aws_vpc.this

state mv is the under-appreciated superpower: refactor resources into a module without destroying anything.

Modules vs. just repeating yourself

Repeat code is sometimes fine in Terraform — explicit beats clever. Reach for a module when the same pattern appears a third time, when teams need a paved-road standard, or when versioning infra changes matters. Below that threshold, plain resources stay more readable.

Well-built modules are how infra scales past "one person who knows where everything is." They're the difference between a codebase and a platform.

Related Articles


Last Updated: October 2026 Author: CloudOpsGuide Team Difficulty: Intermediate Estimated Reading Time: 11 minutes