Skip to main content
RunBook Academy

TerraformXIV · Modules: Reusable Building BlocksProduction Terraform

Why Modules

Foundation⏱ ~12 minbash

What you'll learn

  • Explain what a Terraform module is and is not
  • Identify the cost of copy-pasted configuration across stacks
  • Recognise the right moment to extract a module
  • Use modules as the boundary for policy enforcement and review

Prerequisites

None — start here.

Verified against Terraform CLI 1.9.x · OpenTofu 1.7.x · HCL 2.0 · bpg/proxmox provider 0.66+ · hashicorp/local provider 2.5+ · hashicorp/null provider 3.2+ · hashicorp/random provider 3.6+ · hashicorp/http provider 3.4+ · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · 2026-08-13

Not yet marked complete on this device.

A module is a directory of Terraform configuration. Every Terraform configuration you have ever written is a module — the root module. Anything you call with a module block is a child module. The distinction between “module” and “not module” is purely about how the directory is referenced. The operational question is whether the directory is worth referencing.

This lesson teaches the reason modules exist: not as a convenience for the author, but as a control point for the organisation.

The cost of copy-pasted configuration

Consider a team that operates thirty production stacks. Each stack needs a VPC, three public subnets, three private subnets, an internet gateway, a NAT gateway, and four route tables. The first stack is written by hand. The second stack is copy-pasted from the first. By the twentieth stack, the duplication is invisible — every stack has a slightly different version of the same network resources.

Six months later, security mandates that every VPC includes a flow log to a central S3 bucket. The change is straightforward:

resource "aws_flow_log" "main" {
  iam_role_arn    = aws_iam_role.flow_logs.arn
  log_destination = aws_s3_bucket.flow_logs.arn
  traffic_type    = "ALL"
  vpc_id          = aws_vpc.main.id
}

The team has to apply the change to thirty stacks. The change is applied to twelve. It is missed in eighteen. The audit fails. The remediation takes six engineer-weeks. The cost of the missing module is the cost of the eighteen stacks that were not updated.

What modules buy you

A module is a contract. The author writes the resources once. The author publishes a version. The consumer calls the module with arguments. The consumer does not see the resources. The consumer does not need to know which AWS resource types are involved.

Three properties fall out of that contract:

  1. Single source of truth. The VPC code lives in one repository. The audit is a single git grep. The review is a single pull request.
  2. Versioned interface. The consumer pins a version. A breaking change in the module produces a new major version. The consumer chooses whether to upgrade.
  3. Enforced policy. The module is the place where the policy is enforced. Mandatory tags, mandatory encryption, mandatory logging: all of it lives in the module. The consumer cannot opt out.

Without the module, the policy is a wiki page that nobody reads. With the module, the policy is in the resource declaration. The terraform plan either shows the policy applied or it does not.

The right time to extract a module

The temptation is to extract a module at the first sight of duplication. The discipline is to wait until the duplication is stable. A module is a contract. A contract is hard to change. Extracting too early means the contract is rewritten every month.

A practical heuristic:

  • Two copies. Acceptable. Do not extract yet. The first copy is a draft. The second copy reveals which parts are stable and which parts are still in flux.
  • Three copies. Extract now. The shape is converging. The third copy will follow the first two without much change. A module written at the third copy is cheaper than the maintenance of the unmodularised third copy.
  • One copy plus a roadmap. If the second copy is scheduled, extract the module before the second copy. The first stack consumes the module. The second stack consumes the same module. The first stack is the test bed.

The wrong moment to extract is when the design is still uncertain. A module makes design changes expensive. The correct response is to wait.

The unit of reuse

The unit of reuse is not the resource. The unit of reuse is the thing the consumer wants to refer to. A resource block is a specific cloud object. A module is a logical thing: a network, a database, a load balancer, a service.

A common mistake is to wrap a single resource in a module:

# modules/instance/main.tf
resource "aws_instance" "main" {
  ami           = var.ami
  instance_type = var.instance_type
  tags          = var.tags
}

This module adds nothing. The consumer could have declared the resource directly. The module is a tax. The test for whether a module is doing useful work is whether removing the module would force the consumer to repeat non-trivial configuration. A single resource with three arguments is not a non-trivial configuration.

The module is the review boundary

A module produces a pull request that is reviewable as a unit. A consumer of the module produces a pull request that is reviewable as a configuration choice. The two scopes are different. Reviewing both at the same level is the wrong shape.

When the module is well-defined, the consumer’s pull request looks like:

module "network" {
  source  = "git::ssh://git@github.com/acme/modules.git//network?ref=v3.4.0"
  vpc_cidr = "10.42.0.0/16"
  environment = "production"
}

The reviewer asks: are these three arguments the right values for this stack? The reviewer does not ask: are the subnets configured correctly? That question is answered by the module’s own review.

Performance is not the reason

A common misconception is that modules are an organisational tool that has a performance cost. In Terraform 1.9, a module call adds a small fixed cost to the configuration load. For production configurations — tens of modules, hundreds of resources — the cost is in the low milliseconds. The organisational benefit is not paid for by performance.

The performance cost that does matter is the cost of a module that pulls in more resources than the consumer needs. A database module that also creates a subnet, a security group, a parameter group, and a monitoring alarm forces every consumer to take all of them. If the consumer needs only the database, they pay for the rest. The fix is to split the module, not to remove the module.

What modules are not

  • Modules are not a substitute for state management. A module can be stateful. The state is still per-stack. The module call does not change where the state lives.
  • Modules are not a versioning system. Modules are versioned, but the versioning is the author’s responsibility. The consumer must pin. The contract is not enforced by the tool.
  • Modules are not a security boundary. A module is plain text in a Git repository. Anyone with read access to the repository sees the module. Sensitive values belong in the consumer’s variable values, not in the module’s defaults.
  • Modules are not a workflow engine. A module cannot wait for a manual approval. It cannot pause an apply. It is configuration, not orchestration.

When not to extract

There are situations where a module is the wrong answer:

  • A single resource used once. Wrap it in a module and the consumer pays a tax for no benefit.
  • A configuration that diverges per stack. A module with fifteen optional inputs is a sign that the configuration is not actually shared.
  • A configuration that changes monthly. A module is a contract. A contract rewritten monthly is a tax.
  • A proof of concept. The first stack is a draft. The module will be wrong. Wait.

What comes next

The next lesson is how to structure a module: the directory layout, the file conventions, and the documentation that makes the module usable.

Verification

The lessons in XIV-Modules are conceptual. The verification is the reader’s reasoning about a real module in their own environment.

  • Identify one piece of configuration in your organisation that is copy-pasted across at least three stacks. That configuration is the next module to extract.
  • For that configuration, list the variables a consumer would need to pass. If the list is fewer than ten, the module is well-shaped.
  • Identify the policy that should be enforced in the module. Where in the module would the policy live?
  • Identify the first consumer that would adopt the module. That consumer is the test bed for the module.

Knowledge check · 6 questions

  1. Q1. What is the primary reason modules exist in production Terraform?

  2. Q2. A team has two stacks that share an identical VPC configuration. Should they extract a module now?

  3. Q3. A module that wraps a single resource with three arguments is the right amount of abstraction.

  4. Q4. Where is the right place to enforce a mandatory encryption policy on every S3 bucket?

  5. Q5. Which of the following are properties that fall out of a module contract? (Select all that apply.)

  6. Q6. A team has thirty production stacks with diverged VPC configurations. They want to extract a network module. What is the first step?

Passing score: 75%. Answers are checked in this browser.