No history yet

Complex Deep Merging

Hierarchical Includes

In a sophisticated Terragrunt setup spanning multiple accounts and environments, a single include block is insufficient. We need to layer configurations logically. A common and effective pattern is to stack include blocks, creating a hierarchy that flows from global defaults down to environment-specific overrides.

Consider a structure where you have a root terragrunt.hcl defining global settings like your remote state backend provider. Then, each region has its own terragrunt.hcl to specify the AWS region, and finally, each environment (staging, prod) has a file to set environment-specific variables. Terragrunt evaluates these from the bottom up, with each layer merging on top of the one it includes.

Deep vs. Shallow Merge

The default behavior when Terragrunt merges configurations from multiple include blocks is a shallow merge. For simple values, this is fine. But when dealing with nested maps, such as provider configurations or backend settings, a shallow merge will completely replace the parent map with the child's map. This forces you to redefine the entire nested block, creating the very duplication we're trying to avoid.

The solution is the (deep). By specifying this merge strategy, Terragrunt will recursively merge maps instead of replacing them. This allows you to override or add specific keys within a nested map while inheriting the rest of the attributes from the parent configuration.

# In a parent terragrunt.hcl
locals {
  backend_config = {
    bucket         = "my-global-tfstate-bucket"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}

# In a child terragrunt.hcl
include "root" {
  path   = find_in_parent_folders()
  merge_strategy = "deep"
}

locals {
  backend_config = {
    # We only need to override the key, not the whole map
    key = "path/to/my/module/terraform.tfstate"
  }
}

This example demonstrates how the deep strategy allows the child configuration to simply add the key attribute to the backend_config map. Without it, the child would need to redeclare bucket, region, encrypt, and dynamodb_table.

Lists are a notable exception. Terragrunt does not merge lists. The child's list will always completely overwrite the parent's list, even with the deep merge strategy.

Exposing Attributes

Sometimes, a parent configuration needs a value that is only defined in a child configuration. A classic example is the key for the S3 backend, which is unique to each module. The parent defines the backend bucket, but it can't know the specific state file path for every child module.

This is where expose = true comes in. By adding this attribute to an include block, you can expose all the locals from the child configuration to the parent. This allows the parent to construct configurations using values from the child that includes it.

# In root/terragrunt.hcl (the parent)
remote_state {
  backend = "s3"
  generate = {
    path      = "backend.tf"
    if_exists = "overwrite_terragrunt"
  }
  config = {
    bucket         = "my-global-tfstate-bucket"
    key            = local.tfstate_key # This value comes from the child
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}

# In networking/vpc/terragrunt.hcl (the child)
include "root" {
  path   = find_in_parent_folders()
  expose = true
}

locals {
  # This local is exposed to the parent config above
  tfstate_key = "${path_relative_to_include()}/terraform.tfstate"
}

In this setup, the child's locals block defines a unique tfstate_key. Because of expose = true, the parent remote_state block can reference local.tfstate_key and receive the value defined in the child. This creates a two-way data flow that is essential for building DRY (Don't Repeat Yourself) Terragrunt configurations. The path_relative_to_include() function is another key helper, generating a path based on the child's location relative to the included file.

Let's check your understanding of these advanced merging concepts.

Quiz Questions 1/4

In a hierarchical Terragrunt setup with stacked include blocks (e.g., environment -> region -> root), in what order are the configurations evaluated and merged?

Quiz Questions 2/4

What is the primary problem that merge_strategy = "deep" solves when working with nested maps in Terragrunt include blocks?