IaCInfrastructure as Codeterraformterraform repository

Designing a Production-Ready Terraform Repository

Learn how to organize a Terraform repository for production environments using modules, environments, and best practices that scale with your infrastructure.

Jul 22, 20265 min readUpdated Jul 22, 2026
Designing a Production-Ready Terraform Repository

Designing a Production-Ready Terraform Repository

In the previous articles, we've learned about:

  • How Terraform works
  • Terraform State
  • Terraform Modules
  • Providers
  • Data Sources
  • Everyday Terraform features

At this point, we already know how to write Terraform.

But once Terraform reaches production, another challenge appears.

It's no longer about writing Terraform.

It's about...

How do you keep hundreds of Terraform files maintainable after years of development?

This is where repository design becomes more important than Terraform syntax itself.


Any Structure Works for Small Projects

Most tutorials start with something like this:

terraform/

main.tf
variables.tf
outputs.tf
provider.tf
versions.tf

Or perhaps:

terraform/

network.tf
compute.tf
database.tf
iam.tf

For demos, that's perfectly fine.

Production is different.

A few months later you'll likely have:

  • VPC
  • IAM
  • ECS
  • RDS
  • S3
  • CloudFront
  • Route53
  • Lambda
  • EventBridge
  • Monitoring
  • Secrets
  • ...

Soon you'll end up with dozens of .tf files in a single directory.

Finding a single resource becomes surprisingly difficult.


A Common Mistake

Many teams organize repositories by service.

For example:

terraform/

ec2/
rds/
s3/
iam/
route53/

At first glance, this looks clean.

However, Terraform doesn't operate on directories.

Terraform operates on state.

If everything shares the same state, directory separation is mostly cosmetic.

If every directory owns its own state, dependencies quickly become difficult to manage.


Think About Environments First

Production infrastructure usually consists of multiple environments:

Development

Staging

Production

Each environment has its own:

  • resources
  • state
  • deployment pipeline
  • access permissions

The first thing to separate should be environments.

live/

dev/

staging/

production/

This is also how many large organizations structure their infrastructure repositories.


Then Separate Infrastructure Stacks

For example:

live/

production/

network/

compute/

database/

monitoring/

Each directory represents an independent infrastructure stack.

For example,

network

manages:

  • VPC
  • Subnets
  • NAT Gateway
  • Internet Gateway

While

database

only manages:

  • RDS
  • Parameter Groups
  • Subnet Groups

Each stack owns its own Terraform state.


Modules Should Not Live Inside live

Another common mistake is placing modules under environments.

live/

production/

modules/

vpc/

ecs/

In reality:

Modules are reusable infrastructure code.

Environments are simply consumers of those modules.

A much cleaner approach is:

repository/

modules/

vpc/

ecs/

rds/

live/

dev/

production/

Now,

live

contains only configuration.

While

modules

contains the actual implementation.


What Does a Production Repository Look Like?

A typical production repository often looks like this:

terraform/

modules/

vpc/

ecs/

rds/

iam/

s3/

live/

dev/

network/

compute/

database/

production/

network/

compute/

database/

You'll see this structure in many mature Terraform projects and open-source repositories.


An Environment Should Contain Very Little Code

For example,

live/production/network/main.tf

should not contain thousands of lines.

Instead, it should simply call reusable modules.

code
module "network" {
  source = "../../../modules/vpc"
 
  cidr = "10.0.0.0/16"
 
  environment = "production"
}

That's almost everything it needs.

The real implementation lives inside:

modules/vpc

This approach makes your infrastructure:

  • reusable
  • easier to review
  • safer to update

Where Should Variables Live?

Typically:

modules/

variables.tf
outputs.tf
main.tf

The environment supplies the actual values.

For example:

code
module "database" {
 
  instance_class = "db.t4g.medium"
 
  allocated_storage = 100
 
  backup_retention_period = 30
 
}

Avoid hardcoding values inside modules.

The more generic a module is, the more reusable it becomes.


One State or Multiple States?

This is one of the most frequently asked questions.

Many beginners place the entire infrastructure into a single state.

Everything

↓

terraform.tfstate

This quickly causes problems:

  • Long planning time
  • Slow applies
  • Entire infrastructure becomes locked
  • Even a tiny change requires loading the entire state

Production repositories typically split infrastructure into multiple states.

For example:

network

↓

compute

↓

database

↓

monitoring

Each stack can be deployed independently.

Updating ECS shouldn't lock your networking infrastructure.


Never Let Production Depend on Local State

Some repositories still keep:

terraform.tfstate

inside Git.

This should never happen in production.

Instead, use a remote backend such as:

  • Terraform Cloud
  • Amazon S3
  • Azure Storage
  • Google Cloud Storage

Remote backends provide:

  • State locking
  • Version history
  • Team collaboration
  • Backup
  • Better security

Terraform State is your most valuable asset.

Don't leave it on a developer's laptop.


Naming Conventions Matter

For example:

modules/

aws-vpc

aws-rds

aws-ecs

or

modules/

network

database

compute

The most important thing is consistency.

Avoid mixing styles like:

vpc/

ecs/

RDS/

S3Module/

networking/

Every repository should follow a single naming convention.


Your Repository Will Continue Growing

As infrastructure evolves, your repository will eventually include:

  • CI/CD pipelines
  • Policy as Code
  • Security scanning
  • Cost estimation
  • Drift detection
  • Documentation

A well-designed repository makes all of these additions much easier.


A Real-World Example

A simplified version of the Terraform repository I currently use looks like this:

terraform/

modules/

aws/

vercel/

cloudflare/

supabase/

live/

dev/

aws/

ap-southeast-1/

blog/

production/

aws/

ap-southeast-1/

blog/

shared/

Each provider has its own reusable modules.

Each environment owns its own Terraform state.

Each workload has its own configuration.

This makes Pull Request reviews, deployments, and rollbacks significantly easier.


Lessons I've Learned

After working on multiple infrastructure projects, I've learned that:

  • Repository structure is just as important as Terraform code.
  • Modules should contain implementation, environments should contain configuration.
  • Splitting state by stack reduces deployment risk.
  • Always use a remote backend in production.
  • Don't optimize for today's project—optimize for what your repository will look like in two years.

Terraform is easy to start with.

Keeping the repository clean after thousands of commits is the real challenge.


Summary

Terraform is much more than writing .tf files.

A well-designed repository helps your team:

  • Review infrastructure changes more easily
  • Scale infrastructure safely
  • Reduce conflicts during collaboration
  • Speed up deployments
  • Maintain infrastructure for years

This is the foundation of a scalable Infrastructure as Code platform.


Next Article

So far, we've assumed that Terraform creates every infrastructure resource from scratch.

In reality, most companies already have hundreds of AWS, Azure, or GCP resources before adopting Terraform.

So how do you bring those existing resources under Terraform management without deleting and recreating them?

We'll answer that in the next article:

Import Existing Infrastructure into Terraform.

Comments

0/2000

Loading comments…