As a DevOps consultant, I’m often brought into companies to advise on the best way to structure their Terraform codebase.
While there isn’t a one-size-fits-all approach—since every organization has unique requirements—I’ve developed a structure that works well for most, if not all, companies. In this article, I’ll outline this structure and explain why it’s an effective foundation for managing infrastructure at scale.
The primary advantage of this approach is that it prepares companies to seamlessly manage multiple:
- Cloud providers and other services
- Accounts
- Regions
- Environments
Table of Contents
Separating Modules and State Files into Dedicated Repositories
To maintain a clean and scalable Terraform codebase, it’s best to store all Terraform modules in one repository and all state files in a separate repository. For example, you might use:
- terraform-modules – a repository containing reusable Terraform modules.
- terraform-live – a repository that holds state files and environment configurations.
Using distinct repositories makes it easier to manage infrastructure across multiple environments while keeping modules versioned and reusable.
Here’s the proposed structure for the terraform-modules repository:
terraform-modules
├── dynamodb
├── ecr
├── github-oidc
├── iam-role
├── kms
├── lambda
├── s3
├── sqs
└── ssm
The terraform-modules repository contains reusable Terraform modules, each designed to provision a specific type of resource. By keeping modules separate from the state files, teams can version, test, and update them independently.
Here’s the proposed structure for the terraform-live repository:
terraform-live
└── environments
└── aws
└── my-aws-account
├── _global
│ ├── _bootstrap
│ │ ├── dynamodb
│ │ │ └── terraform-state-locks
│ │ ├── github-oidc
│ │ │ └── github-oidc-provider
│ │ ├── iam-role
│ │ │ └── sops
│ │ ├── kms
│ │ │ ├── sops-key
│ │ │ └── terraform-state-key
│ │ └── s3
│ │ └── terraform-state
│ ├── admin
│ │ └── iam-role
│ │ └── admin
│ ├── github-actions-workflows
│ │ └── iam-role
│ │ ├── github-actions-workflows
│ │ └── github-oidc-auth
│ └── terraform
│ └── iam-role
│ ├── state-manager
│ └── terraform
├── eu-north-1
│ └── dev-stockholm-1
│ ├── dynamodb
│ │ └── table_name
│ ├── ecr
│ │ └── repo_name
│ ├── iam-role
│ │ ├── function_execution_role
│ ├── kms
│ │ └── key_name
│ ├── lambda
│ │ ├── function_name
│ │ │ ├── function_source_code
│ │ │ │ └── __pycache__
│ │ │ └── tests
│ │ │ └── __pycache__
│ ├── s3
│ │ ├── bucket_name
│ ├── sqs
│ │ └── queue_name
This structure ensures that:
- Modules remain independent and reusable, allowing for version control and updates without affecting state files.
- State files and environment configurations are cleanly separated, making it easier to manage infrastructure changes across multiple accounts, regions, and environments.
- Permissions and security controls can be fine-tuned separately for module development and live infrastructure management.
By structuring your Terraform repositories this way, you create a scalable foundation that supports both small and large teams managing complex cloud environments.
Structure Explanation
The terraform-live repository serves as the root of the Git repository where all Terraform state files are stored.
Within terraform-live, you’ll find a directory named environments. This directory is where you organize different cloud providers. In the example above, I’ve included only AWS, but if you’re working with multiple providers (such as GCP or Azure), you can create separate directories for each of them under environments.
Inside the AWS directory, create a subdirectory for each AWS account. These account directories sit at the same level within the AWS directory. This is also where I place the parent terragrunt.hcl file, which defines common settings for all accounts. Within each account directory, place an account.hcl file to store account-level variables such as aws_account_id and other relevant configurations.
Each account directory should contain subdirectories for individual AWS regions. This structure prepares your infrastructure for multi-region deployments.
At the same level as the region directories, you’ll notice a “_global” directory. While “_global” is structured like a region, it actually contains resources that should exist across all regions rather than being region-specific.
Within each region directory, create a subdirectory for each environment. In the example above, I named the environment directory dev-<region_full_name>-1. This pattern helps ensure clarity when managing multiple environments.
This structured approach ensures clarity, scalability, and maintainability, making it easier to manage complex infrastructures across multiple providers, accounts, regions, and environments.
Conclusion: A Scalable and Maintainable Terraform Codebase
Structuring your Terraform codebase in a clear, organized manner is essential for managing infrastructure efficiently—especially as complexity grows. By separating Terraform modules and state files into dedicated repositories, following a hierarchical directory structure, and organizing resources by providers, accounts, regions, and environments, you create a system that is scalable, maintainable, and secure.
Key Benefits of This Approach
✅ Supports multiple cloud providers – Easily extend your infrastructure beyond AWS if needed.
✅ Facilitates multi-account and multi-region setups – Keeps account- and region-specific configurations cleanly separated.
✅ Encourages reuse and versioning of modules – Avoids duplication and simplifies updates.
✅ Enhances security – Allows fine-grained control over permissions and access management.
✅ Simplifies collaboration – Enables teams to work in parallel without stepping on each other’s changes.
Potential Challenges
⚠️ Requires upfront planning – Setting up this structure takes effort but pays off in the long run.
⚠️ May introduce a learning curve – New team members may need time to understand the structure, but clear documentation mitigates this.
⚠️ Needs ongoing maintenance – As infrastructure evolves, keeping the structure up to date is crucial.
By adopting this structured approach, you lay the groundwork for a well-organized, scalable, and secure Terraform codebase—one that grows with your organization and remains easy to manage over time.
Would love to hear your thoughts— leave a comment and tell us how do you structure your Terraform projects?