You built Terraform modules. You documented them. You even got teams to use them.
Six months later, nobody’s touching them. Teams are copy-pasting code again. Your “reusable” infrastructure is gathering dust.
This is the Terraform module paradox: The thing you built to reduce technical debt is now the debt.
Let me show you why—and how to fix it before your next infrastructure sprint.
The Three Silent Killers
Most Terraform module failures follow the same pattern. Here are the three that kill adoption:
- The “Everything” Module (The Terralith) You know the one. A single module that manages VPCs, subnets, security groups, route tables, NAT gateways, endpoints, and probably your coffee machine too.
The problem: When one thing needs to change, everything gets re-deployed. When one team needs a tweak, every consumer breaks.
Real example:
A client had a “networking” module with 47 input variables. Deploying a simple subnet change triggered a 12-minute plan that touched 300+ resources. Teams stopped using it and went back to copy-paste.
What it should be:
Break it down. One module for VPC, one for subnets, one for security groups. Each does one thing well. Changes are surgical, not nuclear.
- Copy-Paste Drift (The Ghost Module) Teams copy your module directory into their project. They make local changes. Now you have 47 slightly different versions of the “same” module across your org.
The problem: You can’t update them. They’re not using your registry. They’re not using your Git source. They’re living in project directories, diverging daily.
What happens: Security patch? You can’t roll it out. Breaking change in AWS? Each team discovers it independently. That module you spent weeks on? It’s now 47 different modules.
What it should be:
Modules live in a registry (public or private) or a versioned Git repo. Teams reference them by version. You update the source. They pull updates intentionally.
- Hardcoded Provider Configurations Your module includes:
provider "aws" {
region = "us-east-1"
}
The problem: Now nobody can use this module in any other region. Or account. Or with an assumed role. You’ve built single-use infrastructure “reusable” code.
What it should be:
Modules NEVER configure providers. The root module does that. Modules declare what providers they need, but don’t configure them.
The Core Misunderstanding
Here’s what most teams get wrong: Modules aren’t about reducing lines of code. They’re about reducing decision fatigue.
A good module doesn’t save keystrokes. It saves meetings.
When an engineer needs to deploy a database, they shouldn’t be debating:
What backup retention makes sense?
Should this be Multi-AZ?
What security group rules are standard?
How do we tag resources consistently?
A good module answers those questions. It encodes decisions. It turns 40 configuration choices into 4.
Bad module: Thin wrapper around a single resource with no added value
Good module: A cohesive service (like “RDS instance with standard backup, monitoring, and security”) that makes smart defaults explicit
The Three Tests Every Module Should Pass
Before you promote a module to “production,” run these tests:
Test 1: The New Hire Test
Can someone who joined yesterday use this module without a 30-minute explanation?
If your module requires a Confluence page, three examples, and a Slack message to understand, it’s too complex.
Fix: Better variable names. Better descriptions. Better examples in your README.
Test 2: The Breaking Change Test
You need to make a breaking change. How many teams will you break?
If the answer is “I don’t know” or “all of them,” your versioning strategy is broken.
Fix: Semantic versioning. Pin major versions. Communicate changes. Give teams time to migrate.
Test 3: The Audit Test
Can you answer “Who’s using version 1.2.3 of this module?” in under 2 minutes?
If you can’t track module usage, you can’t manage lifecycle. You can’t deprecate old versions. You can’t roll out security patches.
Fix: Module registry with usage tracking. Or at minimum, require teams to declare module sources in a discoverable way.
The 80/20 Module Strategy
Stop trying to make modules for everything. Focus on these:
Tier 1: The Standards (Build These First)
VPC with standard CIDR, subnets, NAT setup
RDS instances with backup, monitoring, encryption defaults
ECS/EKS clusters with logging, security defaults
S3 buckets with encryption, versioning, logging
These appear in every project. Standardize them.
Tier 2: The Patterns (Build These Second)
ALB + target group + common routing patterns
Lambda + API Gateway + standard IAM roles
CloudWatch alarms for common metrics
These save time, but aren’t critical to standardization.
Tier 3: Don’t Build (Let Teams Own)
Application-specific resources
Experimental services
One-off configurations
Not everything needs a module. Some things should be written directly in Terraform.
How to Fix Existing Module Debt
You already have module sprawl. Here’s the extraction plan:
Step 1: Audit Usage (Week 1)
Find out what modules exist and who’s actually using them. Not who you think is using them—who actually is.
Tool recommendations:
terraform-rover for discovery
Registry usage metrics if you have a private registry
Git code search for module " blocks
Step 2: Kill the Zombies (Week 2)
Modules nobody’s using? Delete them. Don’t archive. Don’t “deprecate.” Delete.
Every unused module is maintenance debt. Pull the trigger.
Step 3: Fix the Top 5 (Weeks 3-6)
Find your 5 most-used modules. These are probably:
Overly complex
Poorly versioned
Living in random repos
Fix those. Promote them to your registry. Version them properly. Document them.
Ignore everything else until these 5 are solid.
Step 4: Institute Standards (Ongoing)
Before anyone builds another module:
Does this solve a problem for 3+ teams?
Does this encode a decision, not just wrap a resource?
Can we maintain this for 12+ months?
If the answer to any is “no,” don’t build it.
The Real Work
Modules aren’t technical debt because they’re poorly written. They’re technical debt because they’re poorly maintained.
The code you write today will break in 6 months when:
AWS releases a new service version
Your team needs a feature you didn’t plan for
A security team mandates a new requirement
The question isn’t “Is this module perfect?”
The question is “Can we keep this module relevant?”
If you can’t answer “yes,” don’t build it. Write the Terraform directly. Let teams own it.
Stop building module libraries. Start building living infrastructure standards.
That’s the difference between modules that age like wine and modules that age like milk.
Top comments (0)