This repository, along with the terragrunt-infrastructure-live-stacks-example repository, offers a set of best practice infrastructure configurations for setting up a catalog for your infrastructure.
An infrastructure-catalog
is a repository that contains the best practice infrastructure patterns you or your organization wants to use across your infrastructure estate. This is a Git repository that is vetted, and tested to reliably provision the infrastructure patterns you need. You typically version this repository using Semantic Versioning to communicate how changes to infrastructure patterns will impact consumption in your infrastructure estate.
If you have not already done so, you are encouraged to read the Terragrunt Getting Started Guide to get familiar with the terminology and concepts used in this repository before proceeding.
Tip
If you have an existing repository that was started using the terragrunt-infrastructure-modules-example repository as a starting point, follow the migration guide for help in adjusting your existing configurations to take advantage of the patterns outlined in this repository.
To use this repository, you'll want to fork this repository into your own Git organization.
The steps for doing this are the following:
-
Create a new Git repository in your organization (e.g. GitHub, GitLab).
[!TIP] You typically shouldn't have any sensitive information in this repository, as it will only contain generic infrastructure patterns that can be provisioned in any environment, but you might want to have this repository be private regardless.
-
Create a bare clone of this repository somewhere on your local machine.
git clone --bare https://github.com/gruntwork-io/terragrunt-infrastructure-catalog-example.git
-
Push the bare clone to your new Git repository.
cd terragrunt-infrastructure-catalog-example.git git push --mirror <YOUR_GIT_REPO_URL> # e.g. git push --mirror git@github.com:acme/terragrunt-infrastructure-catalog-example.git
-
Remove the local clone of the repository.
cd .. rm -rf terragrunt-infrastructure-catalog-example.git
-
(Optional) Delete the contents of this usage documentation from your fork of this repository.
-
(Optional) Create a release for the new repository (e.g. GitHub, GitLab). You'll want to do this early and often as you make changes to the infrastructure patterns in your fork.
To use this repository, you'll want to make sure you have the following installed:
- Terragrunt
- OpenTofu (or Terraform)
- Go
To simplify the process of installing these tools, you can install mise, then run the following to concurrently install all the tools you need, pinned to the versions they were tested with (as tracked in the mise.toml file):
mise install
For background information on Terragrunt, read the Getting Started Guide.
Note
This code is solely for demonstration purposes. This is not production-ready code, so use at your own risk. If you are interested in battle-tested, production-ready Terragrunt and OpenTofu/Terraform code, continuously updated and maintained by a team of subject matter experts, consider purchasing a subscription to the Gruntwork IaC Library.
This repository contains the following components to help you get started on building out your own infrastructure catalog:
- ec2-asg-service: An example OpenTofu module that provisions an AWS EC2 Auto Scaling Group (ASG) with an Application Load Balancer (ALB) in front of it.
- ecr-repository: An example OpenTofu module that provisions an Amazon Elastic Container Registry (ECR) repository.
- ecs-fargate-service: An example OpenTofu module that provisions an AWS ECS Fargate service for a containerized application.
- iam-role: An example OpenTofu module that provisions an AWS IAM role with configurable policies.
- lambda-service: An example OpenTofu module that provisions an AWS Lambda function and a Lambda function URL to trigger it.
- mysql: An example OpenTofu module that provisions a MySQL database using Relational Database Service (RDS).
- s3-bucket: An example OpenTofu module that provisions an S3 bucket.
- sg: An example OpenTofu module that provisions a security group.
- sg-rule: An example OpenTofu module that provisions security group rules.
- dynamodb-table: An example OpenTofu module that provisions a DynamoDB table.
- ec2-asg-stateful-service: An example Terragrunt unit that provisions the EC2 ASG service with persistent state via integration with a MySQL database.
- ecr-repository: An example Terragrunt unit that provisions an ECR repository.
- ecs-fargate-stateful-service: An example Terragrunt unit that provisions the ECS Fargate service with persistent state via integration with a MySQL database.
- lambda-artifact-s3-bucket: An example Terragrunt unit that provisions an S3 bucket for Lambda artifacts.
- lambda-decoupled-service: An example Terragrunt unit that provisions a decoupled Lambda service.
- lambda-iam-role-to-dynamodb: An example Terragrunt unit that provisions an IAM role for Lambda to access DynamoDB.
- lambda-stateful-service: An example Terragrunt unit that provisions a stateful Lambda service with persistent state via integration with a DynamoDB table.
- mysql: An example Terragrunt unit that provisions a MySQL database using RDS.
- sg: An example Terragrunt unit that provisions a security group.
- sg-to-db-sg-rule: An example Terragrunt unit that provisions a security group rule for database access.
- dynamodb-table: An example Terragrunt unit that provisions a DynamoDB table.
- ec2-asg-stateful-service: An example Terragrunt stack that provisions an EC2 ASG service with an Application Load Balancer (ALB) in front of it, and a MySQL database for state storage. This stack is intended to be provisioned multiple times across multiple environments.
To see example usage for all of these components, see the examples directory.
There are three ways to consume the components in this repository:
- Use the catalog command to scaffold the relevant Infrastructure-as-Code (IaC) for new infrastructure.
- Use the scaffold command to scaffold the relevant IaC for new infrastructure.
- Manually author IaC to use the components in this repository.
The catalog
command is a simple, self-service feature that allows you to quickly scaffold new IaC for usage in your IaC estate.
To use a fork of this repository as a source for a Terragrunt catalog, simply add the following to the root.hcl
file at the root of your Terragrunt project:
catalog {
urls = [
"git::git@github.com:acme/terragrunt-infrastructure-catalog",
]
}
Where github.com/acme/terragrunt-infrastructure-catalog
is the URL for the fork of this repository.
Tip
The git::git@github.com:
syntax is used to explicitly tell Terragrunt to use SSH when cloning the repository. This is likely what you'll need to do if you're using a private fork of this repository.
Once you've configured the catalog, you can create a new directory, navigate into it, and run terragrunt catalog
to see the components available for scaffolding:
mkdir -p live/non-prod/us-east-1/my-lambda-service
cd live/non-prod/us-east-1/my-lambda-service
terragrunt catalog
This will present a list of components you can scaffold.
You can search for a specific component using the /
key, select it using the Enter
key, and press the S
key to scaffold it, which will create the relevant IaC for that component in the current directory.
At the time of writing, the catalog command only supports scaffolding modules as units. Support for more types of components are planned for the future.
For more information, see the Terragrunt Catalog feature documentation.
The catalog
command is most useful when browsing for infrastructure patterns, and you aren't sure exactly what you need to provision.
The scaffold
command is a shortcut that allows you to scaffold an infrastructure pattern directly from the command line, without having to interact with the Terminal User Interface (TUI) of the catalog
command.
To use the scaffold
command, you can run the following command:
terragrunt scaffold <component>
For example, instead of running terragrunt catalog
and then selecting the lambda-service
component, you can run the following instead:
terragrunt scaffold git::git@github.com:acme/terragrunt-infrastructure-catalog//modules/lambda-service
Tip
Take note of the double-slash (//
) in the URL above. This is used to specify the relative path within the repository for the component you want to scaffold.
You can also manually author IaC to use the components in this repository directly. You can see examples of this in the examples directory.
There are three patterns of IaC that you will find in the examples directory:
-
Use OpenTofu modules directly. This is useful if you want to develop new OpenTofu/Terraform modules in isolation, and are working out the right patterns and interfaces for your infrastructure patterns.
We recommend keeping the majority of direct OpenTofu usage like this to the
examples/tofu
directory of this repository, as Terragrunt was designed to help you manage and scale IaC, and leveraging Terragrunt configurations are the best way to do this.Authoring OpenTofu/Terraform code in this repository is a good way to test out patterns and validate interfaces for your infrastructure patterns, before you start leveraging them in your Terragrunt units and stacks.
-
Use Terragrunt Units. This is useful if you want to provision a particular infrastructure pattern one (or a few) time(s) using Terragrunt units. Units are a way to provision OpenTofu/Terraform modules in a systematic way, so that they are reliably reproduced, with business logic and dependencies between them abstracted out of OpenTofu/Terraform code. This helps to keep your OpenTofu/Terraform code simple, generic and easier to maintain.
Terragrunt units are a scalable way to provision OpenTofu/Terraform modules in production, so the examples in examples/terragrunt/units should be usable as-is in your own infrastructure estate in
infrastructure-live
repositories.Units are best used when scoped to solutions for individual point problems (e.g. a single database, a single service, etc.), so that they have a single responsibility, and are easy to reason about, operate and update in isolation.
-
Use Terragrunt Stacks. This is useful if you want to provision multiple infrastructure patterns as a single entity in your infrastructure estate. Terragrunt Stacks are a way to provision multiple Terragrunt units in a systematic way, so that you can reliably reproduce collections of infrastructure across your organization. These are typically used to provision the same infrastructure across multiple environments (e.g.
dev
,staging
,prod
) in a way that is repeatable and easy to reason about, and are a great way to scale your infrastructure.Terragrunt stacks are a great way to provision collections of Terragrunt units as a single entity in your infrastructure estate, so they are typically used to provision full-fledged business solutions (e.g. a complete application, including the database that backs it, the service that runs the compute, etc.).
Stacks are best used when you have established a repeatable pattern for how you want to provision collections of infrastructure patterns across your organization. They operate at a level of abstraction that is higher than that of units, and are a great way to scale your infrastructure once you have a proven pattern for how you want to deploy your infrastructure. If you're new to Terragrunt, you're generally advised to start with modules and units, and to only abstract out a collection of units into a stack once you have a proven pattern for how you want to deploy your infrastructure. If you're an experienced Terragrunt user, you may find it easier to define your infrastructure using stacks from the start.
This is a general guide for how you can update the components in this repository.
git clone
this repository.- Update the code as necessary in the modules directory.
- Go to the examples/tofu directory and confirm that there's an example that corresponds to the usage of the module you just added support for.
- Navigate to the example directory and run
tofu init && tofu plan
. - If the plan looks good, run
tofu apply
. - If the infrastructure works as expected, run
tofu destroy
.
Now that you've given the module a quick sanity check, you can update the appropriate Terratest test in the test/tofu directory to ensure that the module continues to work as expected in the future.
-
Navigate to the test/tofu directory.
-
Run
go test -timeout 60m -count=1 -run Test<Something>
to run the test you just updated.Explanation of the command:
go test
: Run the test as a standard Golang test.-timeout 60m
: Give the test 60 minutes to complete (you can adjust this depending on the expected runtime of the test).-count=1
: Run the test exactly once (Golang tests are automatically opted out of caching when using the-count
flag, so this is a simple way to ensure that caching doesn't result in false positives).-run Test<Something>
: Run the test namedTest<Something>
. If you'd like to run all tests in a directory, you can omit this flag, and if you'd like to run all tests recursively, you can pass./...
as the final argument togo test
instead.
-
If the test passes, you should be confident that the module works as expected.
The Terratest library also supports testing Terragrunt configurations by adjusting the TerraformBinary
field of the terraform.Options
struct.
e.g.
terraformOptions := &terraform.Options{
TerraformDir: "../../../examples/terragrunt/units/ec2-asg-service",
TerraformBinary: "terragrunt",
}
The examples/terragrunt/units directory contains examples of Terragrunt usage to provision OpenTofu/Terraform modules as Terragrunt units.
You can run the Terratests for these examples in the same way as the module tests above.
e.g.
TG_BUCKET_PREFIX='acme-' go test -timeout 60m -count=1 -run Test<Something>
Important
The TG_BUCKET_PREFIX
environment variable is used to set the prefix for the OpenTofu/Terraform state bucket as managed by Terragrunt's Remote State Backend feature. This is used to ensure that the Terragrunt state bucket is unique across all S3 buckets in existence. It's recommended to either set this environment variable to a short prefix that represents your organization (e.g. acme-
), or to update the value in examples/terragrunt/root.hcl to hard-code a value unique to your organization for the bucket
attribute.
Testing OpenTofu/Terraform modules as Terragrunt units is useful to ensure that the module can be reliably provisioned as a Terragrunt unit in the future. It can also be useful to validate that certain implementation details of how the module is meant to be consumed can be done reliably. For example, you may want to test that application code that a module depends on is packaged correctly by the hooks used in the unit, or that the right interface has been exposed for consumption by the unit.
The units found in the units directory are intended to be used as part of a stack, referenced by terragrunt.stack.hcl
files.
As such, you typically won't find the units in units used directly in isolation in the examples/terragrunt/units directory. Instead, you'll find them used as part of a stack in the examples/terragrunt/stacks directory. You will also find collections of these units, leveraged in the stacks directory, used in some of these examples.
The process for updating the units and stacks is the same as updating the modules.
- Update the code as necessary in the units and stacks directories.
- Update the examples in the examples/terragrunt/stacks directory to reflect the changes you made to them.
- Navigate to the relevant example directory, and run the
terragrunt stack plan
andterragrunt stack apply
commands to test the changes you made. - If the changes work as expected, you can run the
terragrunt stack destroy
command to clean up the infrastructure you just provisioned. - Like with modules, you can update the Terratests in the test/terragrunt/stacks directory to ensure that the stack continues to work as expected in the future.
When you're done testing changes locally, the following steps are how you release a new version:
- Update the code as necessary.
- Commit your changes to Git:
git commit -m "commit message"
. - Create a new release. (e.g. on GitHub, go to the releases page and click "Draft a new release").
- Now you can reference the new Git tag (e.g.
v0.0.2
) in theref
query string parameter of thesource
attribute in yourterragrunt.hcl
orterragrunt.stack.hcl
file.
This repository uses the following folder structure:
modules/
: Contains reusable OpenTofu/Terraform modulesunits/
: Contains Terragrunt units that provision OpenTofu/Terraform modulesstacks/
: Contains Terragrunt stacks that provision collections of Terragrunt unitsexamples/
: Contains example code showing how to use the modules, units, and stackstest/
: Contains automated tests for the examples.circleci/
: Contains CI/CD configuration.pre-commit-config.yaml
: Contains pre-commit hook configurations
This repo is an example of a monorepo, where you have multiple modules, units and stacks in a single repository. There are benefits and drawbacks to using a monorepo vs. using a polyrepo - one module/unit/stack per repository. Which you choose depends on your tooling, how you build/test OpenTofu/Terraform modules, and so on. Regardless, the live repo will consume the components in the same way: a reference to a Git release tag in a terragrunt.hcl
/terragrunt.stack.hcl
file.
- Easier to make global changes across the entire codebase. For example, applying a critical security fix or upgrading everything to a new version of Terraform/OpenTofu can happen in one logical commit.
- Easier to search across the entire codebase. You can search through all the module code using a standard text editor or file searching utility just with one repo checked out.
- Simpler continuous integration across modules. All your code is tested and versioned together. This reduces the chance of late integration - issues arising from out-of-date module-dependencies.
- Single repo and build pipeline to manage. Permissions, pull requests, etc. all happen in one spot. Everything validates and tests together so you can see any failures in one spot.
- Harder to keep changes isolated. While you're modifying module
foo
, you also have to think through whether this will affect modulebar
. - Ever-increasing testing time. The simple approach is to run all tests after every commit, but as the monorepo grows, this gets slower and slower (and more brittle).
- No dependency management system. To only run a subset of the tests or otherwise validate only changed components, you need a way to tell which components were affected by which commits.
- No feature toggle support. OpenTofu/Terraform don't support feature toggles, which are often critical for making large scale changes in a monorepo (Terragrunt does, however).
- Release versions change even if component code didn't change. A new "release" of a monorepo involves tagging the repo with a new version. Even if only one component changed, all the components effectively get a new version. This can be especially problematic when introducing a breaking change, as it will require that consumption of any component be done more carefully, even if the component didn't change at all.
- Easier to keep changes isolated. You mostly only have to think about the one component/repo you're changing rather than how it affects other components.
- Testing is faster and isolated. When you run tests, it's just tests for this one component, so no extra tooling is necessary to keep tests fast.
- Easier to detect individual component changes. With only one component in a repo, there's no guessing at which component changed as releases are published.
- Harder to make global changes. Changes across repositories require lots of checkouts, separate commits and pull requests, and an updated release per module.
- Harder to search across the codebase. Searches require checking out all the repositories or having tooling (e.g., GitHub or Azure DevOps) that allows searching across repositories remotely.
- No continuous integration across components. You might make a change in your component, and the teams that depend on that component might not upgrade to the new version for a long time.
- Many repositories and builds to manage. Permissions, pull requests, build pipelines, test failures, etc. get managed in several places.
- Potential dependency graph problems. It is possible to run into issues like "diamond dependencies" when using many components together (See Dependency Hell).
- Slower initialization. OpenTofu/Terraform downloads each dependency from scratch, so if one repo depends on components from many other repos, it will download that component every time it's used rather than just once.