DRY Configuration with include
The idea in one sentence
Section titled “The idea in one sentence”The include block is how a unit’s terragrunt.hcl pulls in everything defined in root.hcl — or in any intermediate config file — without retyping any of it.
include and find_in_parent_folders
Section titled “include and find_in_parent_folders”Last lesson’s vpc/terragrunt.hcl opened with this block, and it deserves a proper explanation now:
include "root" { path = find_in_parent_folders("root.hcl")}find_in_parent_folders("root.hcl") walks up the directory tree from wherever the current unit lives, one directory at a time, until it finds a file literally named root.hcl. include "root" then merges everything that file defines into the current unit’s configuration, as though it had been typed there directly — the remote_state block, the generated backend.tf, the provider generate block, all of it.
Why the search matters: it means the exact same include line works whether a unit sits one directory below root.hcl or five directories deep. Move a unit into a new subdirectory for a reorganization, and this line keeps working unchanged — it never hard-codes a relative path like ../../../root.hcl that would break the moment the tree is reshaped.
locals and read_terragrunt_config for one level down
Section titled “locals and read_terragrunt_config for one level down”include is not the only inheritance mechanism, and it is not always the right one — sometimes you want to share a smaller, more specific value with only part of the tree, not the whole thing. Picture an intermediate region.hcl file sitting one level below the true root, covering just the units that belong to a specific AWS region:
locals { aws_region = "us-east-1"}A unit under that region’s directory reads it with read_terragrunt_config(), combined with the same find_in_parent_folders() you already know:
include "root" { path = find_in_parent_folders("root.hcl")}
locals { region_vars = read_terragrunt_config(find_in_parent_folders("region.hcl"))}
terraform {}
inputs = { aws_region = local.region_vars.locals.aws_region cidr_block = "10.0.0.0/16"}read_terragrunt_config() parses another Terragrunt config file and hands back its contents as a value, and that file’s own locals block ends up nested under .locals on the result — hence local.region_vars.locals.aws_region. A unit under prod/ap-southeast-1/ would have its own region.hcl defining aws_region = "ap-southeast-1", while every unit anywhere in the tree still shares the same root.hcl for its backend and provider. Same root, different region per subtree, without a single unit hard-coding which region it belongs to inline.
inputs: the Terragrunt equivalent of a tfvars file
Section titled “inputs: the Terragrunt equivalent of a tfvars file”Every example so far has ended with an inputs = { ... } block, and it deserves its own explanation. This is how a unit supplies its underlying Terraform module’s input variables — functionally the same job a .tfvars file does for a plain Terraform root module, except expressed directly as Terragrunt HCL rather than as a separate file Terraform has to be told to load. Each key in inputs maps to a variable the module declares, and Terragrunt passes the whole block through to Terraform as -var values under the hood.
Because inputs is regular HCL rather than a static .tfvars file, it can reference anything else in the same config — local.region_vars.locals.aws_region above is one example, and the next lesson introduces referencing another unit’s outputs the same way.
flowchart TD root["root.hcl (remote_state + generate)"] region["prod/us-east-1/region.hcl (locals.aws_region)"] unit["prod/us-east-1/vpc/terragrunt.hcl"] root -->|include + find_in_parent_folders| unit region -->|read_terragrunt_config + find_in_parent_folders| unit unit --> inputs["inputs = aws_region, cidr_block"]