Modules: Building Reusable Infrastructure Components

How I learned to stop copying code and start building composable infrastructure


Table of Contents


Introduction: The Copy-Paste Nightmare

Three months into my Terraform journey, I had a problem. A big one.

I was managing infrastructure for five different microservices. Each service needed:

  • A configuration file

  • A secrets file

  • A deployment manifest

  • A monitoring configuration

So I did what any reasonable person would do: I copied the Terraform code five times and changed the service names.

It worked... until I needed to update the configuration template. Then I had to update it in five places. And of course, I forgot to update one of them. Production broke at 2 AM.

That's when a senior engineer saw my code during review.

"Why are you copying this?" they asked.

"Because each service needs its own configuration," I said defensively.

"No," they said. "You need modules. You're doing the equivalent of copying functions instead of calling them."

They refactored my 500 lines of duplicated code into a single 50-line module that could be called five times. Each service got its configuration with three lines of code.

My mind was blown. 🤯

This article is everything I learned about Terraform modules: the building blocks that transform infrastructure code from repetitive scripts into reusable, composable, maintainable components.


What Are Terraform Modules?

A module is a container for multiple resources that are used together. It's essentially a package of Terraform configuration.

The Simplest Module

Every Terraform configuration is actually a module! Even this:

This is called the root module - the working directory where you run terraform apply.

Calling a Module

Modules become powerful when you call them:

This calls a module stored in ./modules/service and passes it inputs.

Module Structure


Why Modules Matter

1. DRY (Don't Repeat Yourself)

Without modules:

With modules:

2. Abstraction

Modules hide complexity:

3. Reusability

Write once, use everywhere:

4. Testing

Modules can be tested independently:

5. Versioning

Modules can be versioned:


Module Basics

Module Components

Every well-designed module has three files:

Optional files:

Module Communication Flow

spinner

Flow:

  1. Root module passes inputs to child module

  2. Child module creates resources

  3. Resources have attributes

  4. Module exposes attributes as outputs

  5. Root module can use those outputs


Creating Your First Module

Let's build a simple module step by step.

Project Structure

Step 1: Create Module Directory

Step 2: Define Module Variables

modules/greeting/variables.tf:

Step 3: Create Module Resources

modules/greeting/main.tf:

Step 4: Define Module Outputs

modules/greeting/outputs.tf:

Step 5: Use the Module

main.tf:

Step 6: Apply

Output:

Files created:

  • greeting-John.txt → "Hello, John."

  • greeting-Sarah.txt → "Welcome, Sarah!!!"

  • greeting-Alex.txt → "Goodbye, Alex."


Module Inputs (Variables)

Module inputs define the interface to your module.

Input Best Practices

1. Clear descriptions:

2. Sensible defaults:

3. Complex types for structured data:

4. Validation:


Module Outputs

Outputs expose module results to the calling module.

Output Patterns

1. Resource attributes:

2. Computed values:

3. Sensitive outputs:

4. Structured outputs:


Calling Modules

Module Block Syntax

Accessing Module Outputs

Module Dependencies


Module Sources

Terraform supports multiple module sources:

1. Local Paths

2. Terraform Registry

3. GitHub

4. Generic Git

5. HTTP URLs

6. S3 Buckets


Module Composition

Modules can call other modules, creating a hierarchy.

Simple Composition

modules/application/main.tf:

Root main.tf:

Module Communication

Modules can pass outputs to each other:

Module Composition Diagram

spinner

Terraform Registry

The Terraform Registryarrow-up-right is a repository of public modules.

Using Registry Modules

Example - AWS VPC module:

Finding Modules

Search the registry:

  • Browse by provider: AWS, Azure, GCP

  • Browse by category: Network, Compute, Security

  • Search by keyword: "vpc", "kubernetes", "monitoring"

Quality indicators:

  • ✅ Verified modules (HashiCorp verified)

  • ⭐ Star count (community endorsement)

  • 📝 Documentation quality

  • 🔄 Recent updates

  • 📊 Download count

Module Versioning

Semantic versioning (SemVer):

Version constraints:


Module Best Practices

1. Single Responsibility

Each module should do one thing well:

Good:

Bad:

2. Clear Interfaces

Define clear inputs and outputs:

3. Sensible Defaults

Provide defaults for non-critical inputs:

4. Avoid Hardcoding

Use variables instead of hardcoded values:

Bad:

Good:

5. Use Locals for Computed Values

6. Document Your Module

README.md:

Inputs

Name
Type
Default
Description

service_name

string

-

Service name (required)

port

number

-

Service port (required)

environment

string

"dev"

Deployment environment

Outputs

Name
Description

config_file

Path to configuration file

service_url

Full service URL

Then reference specific versions:


Real-World Example: Multi-Service Blog Platform

Let's build a complete blog platform using modules.

Project Structure

Service Module

modules/service/variables.tf:

modules/service/main.tf:

modules/service/outputs.tf:

Monitoring Module

modules/monitoring/variables.tf:

modules/monitoring/main.tf:

modules/monitoring/outputs.tf:

Deployment Module

modules/deployment/variables.tf:

modules/deployment/main.tf:

modules/deployment/outputs.tf:

Root Module

variables.tf:

main.tf:

outputs.tf:

Environment Configuration

environments/production.tfvars:

Module Dependency Graph

spinner

Deploy

Result:

  • ✅ 4 services configured (web, api, admin, worker)

  • ✅ Each service scaled by tier (enterprise = 5x)

  • ✅ Configuration files generated

  • ✅ Secrets created

  • ✅ Monitoring configured

  • ✅ Docker Compose ready

  • ✅ Deployment manifest created


Module Versioning

Semantic Versioning for Modules

Creating Versions

Using Versioned Modules

Version Constraints


Testing Modules

Manual Testing

Create a test directory:

test/main.tf:

Run test:

Automated Testing with Terratest

test/service_test.go:


Publishing Your Module

Terraform Registry Requirements

  1. GitHub repository

  2. Standard module structure

  3. Semantic versioning (Git tags)

  4. README.md with usage examples

  5. LICENSE file

Module Structure

README.md Template

Examples

Requirements

Name
Version

terraform

>= 1.0

local

~> 2.4

random

~> 3.5

Inputs

Name
Type
Default
Required
Description

name

string

-

yes

Service name

environment

string

-

yes

Environment

config

object

-

yes

Service configuration

Outputs

Name
Description

service_name

Full service name

config_file

Configuration file path

info

Complete service information

License

MIT

  1. Sign in to Terraform Registry:

    • Go to https://registry.terraform.io/

    • Sign in with GitHub

    • Publish module

  2. Module is now available:


Common Module Patterns

Pattern 1: Conditional Resource Creation

Pattern 2: Default Values with Merge

Pattern 3: Computed Resource Names

Pattern 4: Feature Flags

Pattern 5: Multi-Environment Configuration


What I Learned About Module Design

1. Start Simple, Refactor Later

Don't try to create the perfect module immediately:

First iteration:

After seeing patterns:

2. Inputs Are Your API

Treat module inputs like a public API:

  • Clear names: replicas not num

  • Good defaults: Most inputs should be optional

  • Validation: Catch errors early

  • Documentation: Every variable needs description

3. Outputs Are Your Contract

Expose useful information:

  • Resource IDs

  • Computed values

  • Structured data for downstream modules

4. Composition Over Complexity

Bad - One giant module:

Good - Composed modules:

5. Test in Isolation

Each module should work standalone:


Next Steps

Congratulations! You've mastered Terraform modules:

✅ Module fundamentals ✅ Creating custom modules ✅ Module inputs and outputs ✅ Module sources (local, registry, Git) ✅ Module composition ✅ Terraform Registry ✅ Versioning and publishing ✅ Real-world multi-service platform ✅ Testing and best practices

Practice Exercises

Exercise 1: Refactor to Modules

Exercise 2: Create a Module Library

Exercise 3: Publish a Module

Coming Up Next

In Article 6: State Management - Understanding and Managing Terraform Statearrow-up-right, we'll dive deep into:

  • What is Terraform state?

  • State file structure

  • Remote state backends

  • State locking

  • State migration

  • Troubleshooting state issues

Modules organize your code. State tracks your infrastructure. Together, they're the foundation of production Terraform.


This Week's Challenge: Refactor your existing infrastructure code into modules. Aim for 80% reduction in duplication. Share your module on GitHub!

See you in Article 6! 🏗️


"The day I learned modules was the day my infrastructure code went from 2,000 lines of copy-paste to 200 lines of elegant composition. I'll never go back." - Me, after deleting 1,800 lines of duplicated code

Last updated