Recommended Folder Structure
A frequent question we get from customers is how to structure their Terragrunt code. In this document, we cover Gruntwork's recommendations for where your code should live (and which code should live where) and how to structure your code to optimize for comprehension, scale, and development speed.
Separating modules from live infrastructure
We recommend separating the implementation of your Terraform modules and the usage of modules into separate repos. The primary reason is so you can version your modules and run different versions of those modules in different environments: e.g., you might run
v1.0.0 of your EKS module in
prod while testing out
v2.0.0 of the EKS module in
stage. This means your Terraform code will be spread out across (at least) two repositories:
modules: This repo defines reusable modules. Think of each module as a “blueprint” that defines a specific part of your infrastructure.
live: This repo defines the live infrastructure you’re running in each environment (stage, prod, mgmt, etc.). Think of this as the “houses” you built from the “blueprints” in the modules repo.
In the next section, we'll focus on how to organize your
live infrastructure repository. Structuring your
modules repository is outside of the scope of this document.
Live infrastructure repository
To meet the goal of organizing code to optimize for comprehension, scale, and development speed. Gruntwork has developed an approach that structures code that organizes Terragrunt modules by account, region, environment, and category.
Suggested folder hierarchy
Below is an example folder structure:
A full example of this structure can be seen in our example
Next, we'll walk through each level of the folder structure and how it is used.
At the top level of the folder structure are folders for each of your AWS accounts, such as
mgmt, etc. If you have everything deployed in a single AWS account, there will just be a single folder at the root (e.g.
Within each account, there will be one or more AWS regions, such as
ap-southeast-2, where you've deployed resources. There may also be a
_global folder that defines resources that are available across all the AWS regions in this account, such as IAM users, Route 53 hosted zones, and CloudTrail.
Within each region, there will be one or more "environments", such as
stage, etc. Typically, an environment will correspond to a single AWS Virtual Private Cloud (VPC), which isolates that environment from everything else in that AWS account. There may also be a
_global folder that defines resources that are available across all the environments in this AWS region, such as Route 53 A records, SNS topics, and ECR repos.
Within each Environment, you deploy all the resources for that environment, such as EKS clusters and Aurora databases, using Terraform modules. Groups or similar modules inside an environment are further organized by the overarching category they relate to, such as networking (VPCs) and services (EKS workers).
Within each environment, you deploy all the resources for that environment, such as EC2 Instances, Auto Scaling Groups, ECS Clusters, Databases, Load Balancers, and so on. These are represented as a directory and a
terragrunt.hcl file. For example
vpc/terragrunt.hcl. Care should be taken to ensure these modules are "right sized" to optimize for ease of understanding, security, and risk (e.g., smaller modules have lower blast radius).
When working to "right size" a module, consider that large modules are considered harmful, but that you also don't want a single module to deploy a single resource, do to the number of applies it would require to provision an entire environment. You need to find a good balance, grouping things that are typically deployed together, have similar deployment cadences, have similar risk/security profiles, have common team ownership, etc.
For example, you might have one module that handles all your networking; another module that sets up your data stores (e.g., RDS); another module that handles your orchestration tool (e.g., EKS); and perhaps a bunch of individual modules to deploy apps, each owned by a separate team.
Gruntwork recommends storing state per resource. As an example, if you had an account named
dev in region
us-east-1, with the environment
dev, and a Terragrunt module defined in the category
networking and resource
vpc (for a full path of
dev/us-east-1/dev/networking/vpc/terragrunt.hcl), your remote state would be configured for
Please see the Terragrunt documentation on keeping your remote state configuration dry for more information.
Typically, you have a hierarchy of variables: some that are truly global across all accounts/environments; some that apply across a single account/environment; some that apply across a single region; some that apply across a set of services; etc.
There are multiple ways to handle this, depending on the use case, but the most common pattern is:
- Put reusable variables at the right "level" within your hierarchy in an
find_in_parent_foldersto automatically load the data from the file from the appropriate place in the hierarchy.
For more details and the other options, see Keep your Terragrunt Architecture DRY.
Now that you have your accounts, environments, categories, resources, and state figured out, you may be wondering how you can increase code re-use across your
live repository. Gruntwork recommends using the "module defaults" pattern to keep your Terragrunt architecture DRY.