Common Configurations with Terragrunt

Terragrunt supports multiple included files, which is a great way to have common configurations with module definitions and default values included in any child configurations. Here is a short explanation of how it works.

Example

Usually, when I write a Terragrunt module, I define the following blocks:

First, I define a Terraform block with a module source.

terraform {
  source = "git::[email protected]:example/modules.git//aws/vpc?ref=v0.10.0"
}

Then, I define a Terragrunt block to include the root terragrunt.hcl file with backend, provider, and other configurations across all of my environments and accounts.

include {
  path = find_in_parent_folders()
}

The find_in_parent_folders() method traverses the directory tree beginning with the current terragrunt.hcl file and returns the absolute path to the first terragrunt.hcl in a parent folder. This block is required in any Terragrunt repository with a parent-child structure.

Finally, I can define the inputs block to pass all the required values to the module:

inputs = {
  name  = "example-vpc"
  other = "example-value"
  ...
}

Here is the resulting child terragrunt.hcl:

terraform {
  source = "git::[email protected]:example/modules.git//aws/vpc?ref=v0.10.0"
}

include {
  path = find_in_parent_folders()
}

inputs = {
  name  = "example-vpc"
  other = "example-value"
  ...
}

Problem

Let’s say I need to define 10 configurations with the same module. By using the same static config, I’ll end up repeating the same Terraform source with locked module versions and most of the inputs.

In order to write less code and have more dynamic definitions, I can define a common configuration for the same module and then include it in the child terragrunt.hcl. Here is an example:

Let’s create a file example-module.hcl in the common folder:

locals {
  module_url     = "git::[email protected]:example/modules.git//aws/vpc"
  module_version = "v0.10.0"
}

terraform {
  source = "${local.module_url}?ref=${local.module_version}"
}

inputs = {
  other = "example-value"
}

Now the child terragrunt.hcl configurations can include this file in order to have the same module definition and same inputs across all environments and accounts with the ability to override them in the child configurations.

Solution

Let’s add everything together in a new terragrunt.hcl child configuration file.

First, define the root terragrunt.hcl include block and give it a name:

include "root" {
  path = find_in_parent_folders()
}

Then, include the common module config:

include "module" {
  path = "${get_path_to_repo_root()}/common/modules/example-module.hcl"
}

Finally, define inputs that are unique for the specific resource:

inputs = {
  name = "example-vpc"
}

Here is the resulting child terragrunt.hcl configuration with a dynamically included module:

include "root" {
  path = find_in_parent_folders()
}

include "module" {
  path = "${get_path_to_repo_root()}/common/modules/example-module.hcl"
}

inputs = {
  name = "example-vpc"
}

During the Terragrunt run, the common module configuration will be merged with the child terragrunt.hcl configuration, merging all blocks and inputs into one configuration. It works with Terragrunt out of the box, and it also works with the Terragrunt config generator if you use Atlantis for your CI/CD pipelines.

Now, when you want to bump the example-module version, you only need to do it once in the common/example-module.hcl file and all the child configurations will pick up the change during subsequent Terragrunt runs. Plus, if you want to override some common input value or module source, just do it in the child configuration and it will take precedence over the included one.

You can see a similar example of the same pattern in the official Terragrunt live infrastructure example repo.