Skip to content

autoinclude

stack

Usage

The autoinclude block goes inside a unit or stack block in a terragrunt.stack.hcl file. It declares dependencies between the components of a stack, and other configuration they need, without editing the unit sources in your catalog.

terragrunt stack generate writes the block’s body to terragrunt.autoinclude.hcl in the unit’s generated directory. Terragrunt merges that file into the unit’s configuration whenever it parses the unit.

terragrunt.stack.hcl
unit "vpc" {
source = "../catalog/units/vpc"
path = "vpc"
}
unit "app" {
source = "../catalog/units/app"
path = "app"
autoinclude {
dependency "vpc" {
config_path = unit.vpc.path
mock_outputs = {
vpc_id = "vpc-mock"
}
}
inputs = {
vpc_id = dependency.vpc.outputs.vpc_id
}
}
}
app/terragrunt.autoinclude.hcl
# Generated by Terragrunt from an autoinclude block. Do not edit manually.
dependency "vpc" {
config_path = "../vpc"
mock_outputs = {
vpc_id = "vpc-mock"
}
}
inputs = {
vpc_id = dependency.vpc.outputs.vpc_id
}
  • terragrunt.stack.hcl
  • Directory.terragrunt-stack
    • Directoryvpc
      • terragrunt.hcl
    • Directoryapp
      • terragrunt.autoinclude.hcl merged into the unit’s configuration
      • terragrunt.hcl

In a stack block, the file is terragrunt.autoinclude.stack.hcl, next to the nested stack’s terragrunt.stack.hcl. See Stack-level autoinclude.

Body contents

The block takes no label and has no attributes of its own. In a unit block, its body can contain:

  • dependency blocks. config_path usually reads a component path variable. Every other attribute works as it does in a regular dependency block.
  • The inputs attribute.
  • Any other block a unit configuration accepts, e.g. feature, errors, generate, or remote_state.

Terragrunt rejects:

  • A locals block. Declare locals in terragrunt.stack.hcl instead.
  • The values attribute, which a unit configuration doesn’t accept. Set values on the unit block instead.
  • An autoinclude block.

Expressions

Terragrunt resolves every function and variable in the body when it generates the stack, in the context of the stack file, so get_terragrunt_dir() returns the stack file’s directory. The exception is dependency.*, which Terragrunt writes as is, because dependency outputs only exist when the unit runs. For a stack file in /repo/live:

terragrunt.stack.hcl
locals {
env = "prod"
}
unit "vpc" {
source = "../catalog/units/vpc"
path = "vpc"
}
unit "app" {
source = "../catalog/units/app"
path = "app"
autoinclude {
dependency "vpc" {
config_path = unit.vpc.path
}
inputs = {
env = local.env
dir = get_terragrunt_dir()
vpc = dependency.vpc.outputs
}
}
}
app/terragrunt.autoinclude.hcl
dependency "vpc" {
config_path = "../vpc"
}
inputs = {
env = "prod"
dir = "/repo/live"
vpc = dependency.vpc.outputs
}

Component path variables

Each unit and stack block gets a variable, unit.<name>.path or stack.<name>.path, with the full path of the directory stack generate writes it to:

/repo/live/terragrunt.stack.hcl
unit "vpc" {
source = "../catalog/units/vpc"
path = "vpc"
}

Here unit.vpc.path is /repo/live/.terragrunt-stack/vpc. With no_dot_terragrunt_stack = true, it would be /repo/live/vpc.

When a dependency’s config_path reads one of these variables, Terragrunt writes the path relative to the generated unit, e.g. "../vpc" in the example above.

autoinclude blocks and the values attribute of unit and stack blocks can read these variables. Only components declared in the same stack file are visible, and referring to an undeclared name fails generation.

To depend on a unit from inside a nested stack, pass its path down through values. values.vpc_path holds the full path of the vpc directory:

terragrunt.stack.hcl
unit "vpc" {
source = "../catalog/units/vpc"
path = "vpc"
}
stack "apps" {
source = "../catalog/stacks/apps"
path = "apps"
values = {
vpc_path = unit.vpc.path
}
}
catalog/stacks/apps/terragrunt.stack.hcl
unit "api" {
source = "github.com/acme/units//api"
path = "api"
autoinclude {
dependency "vpc" {
config_path = values.vpc_path
}
}
}

stack generate still writes config_path relative to the generated api unit:

.terragrunt-stack/apps/.terragrunt-stack/api/terragrunt.autoinclude.hcl
dependency "vpc" {
config_path = "../../../vpc"
}

An expanded component has a path for each instance. Read one by indexing the variable with the instance’s key from for_each, or its index from count, as you would its address in stack output. Here app depends on the us-east-1 instance of vpc:

terragrunt.stack.hcl
unit "vpc" {
expansion {
for_each = toset(["us-east-1", "us-west-2"])
}
source = "../catalog/units/vpc"
path = "vpc/${each.key}"
}
unit "app" {
source = "../catalog/units/app"
path = "app"
autoinclude {
dependency "vpc" {
config_path = unit.vpc["us-east-1"].path
}
}
}

stack generate writes config_path = "../vpc/us-east-1" into the app unit. To depend on every instance, see With expansion.

Merge behavior

Terragrunt merges terragrunt.autoinclude.hcl into the unit’s configuration as it does a regular include with the default strategy: a shallow merge in which the autoinclude wins. A block or attribute set in only one file is kept. One set in both comes from the autoinclude, except inputs, which combine key by key. An input set in both still takes the autoinclude’s value whole, without merging nested maps.

app/terragrunt.hcl
terraform {
source = "../modules/app"
}
dependency "vpc" {
config_path = "../old-vpc"
}
inputs = {
name = "app"
tags = { team = "web" }
}
app/terragrunt.autoinclude.hcl
dependency "vpc" {
config_path = "../vpc"
mock_outputs = {
vpc_id = "vpc-mock"
}
}
inputs = {
env = "prod"
tags = { owner = "ops" }
}

The unit runs with this configuration, which terragrunt render prints. The marked lines come from the autoinclude:

terraform {
source = "../modules/app"
}
dependency "vpc" {
config_path = "../vpc"
mock_outputs = {
vpc_id = "vpc-mock"
}
}
inputs = {
env = "prod"
name = "app"
tags = { owner = "ops" }
}

With expansion

When the unit or stack block has an expansion block, Terragrunt writes a file for each instance, and each.key, each.value, and count.index in the autoinclude body resolve to that instance.

A dependency block in the body can have its own expansion block:

  1. Terragrunt evaluates its for_each or count in the stack file’s context and writes the result into the generated expansion block as a literal value.
  2. It resolves config_path once per instance and writes it as a lookup by each.key or count.index.
  3. Other each.* and count.index references in the dependency block stay as written, and the generated unit expands the dependency again when Terragrunt parses it.

A vpc unit expands over two regions:

terragrunt.stack.hcl
locals {
regions = toset(["us-east-1", "us-west-1"])
}
unit "vpc" {
expansion {
for_each = local.regions
}
source = "../catalog/units/vpc"
path = "vpc/${each.key}"
values = {
region = each.key
}
}

The app unit depends on every instance:

terragrunt.stack.hcl
unit "app" {
source = "../catalog/units/app"
path = "app"
autoinclude {
dependency "vpc" {
expansion {
for_each = local.regions
}
config_path = unit.vpc[each.key].path
mock_outputs = { vpc_id = "vpc-mock-${each.key}" }
}
inputs = {
vpc_ids = {
for region, vpc in dependency.vpc : region => vpc.outputs.vpc_id
}
}
}
}

stack generate writes:

.terragrunt-stack/app/terragrunt.autoinclude.hcl
# Generated by Terragrunt from an autoinclude block. Do not edit manually.
dependency "vpc" {
expansion {
for_each = toset(["us-east-1", "us-west-1"])
}
config_path = {
us-east-1 = "../vpc/us-east-1"
us-west-1 = "../vpc/us-west-1"
}[each.key]
mock_outputs = { vpc_id = "vpc-mock-${each.key}" }
}
inputs = {
vpc_ids = {
for region, vpc in dependency.vpc : region => vpc.outputs.vpc_id
}
}

When the unit block and the dependency block are both expanded, the dependency’s for_each or count reads the unit’s instance. Everywhere else in the dependency block, each and count refer to the dependency’s own instance.

Depending on a sibling stack

A dependency’s config_path can read stack.<name>.path to depend on a nested stack. The dependency’s outputs then include every unit the stack generates, keyed by unit and nested stack name, as described in Depending on a stack.

Here the network stack contains a unit named vpc with a vpc_id output, so app reads it as dependency.network.outputs.vpc.vpc_id:

terragrunt.stack.hcl
stack "network" {
source = "../catalog/stacks/network"
path = "network"
}
unit "app" {
source = "../catalog/units/app"
path = "app"
autoinclude {
dependency "network" {
config_path = stack.network.path
mock_outputs_allowed_terraform_commands = ["validate", "plan"]
mock_outputs = {
vpc = {
vpc_id = "mock-vpc"
}
}
}
inputs = {
vpc_id = dependency.network.outputs.vpc.vpc_id
}
}
}

Stack-level autoinclude

In a stack block, the autoinclude body can contain only unit and stack blocks. Terragrunt writes them to terragrunt.autoinclude.stack.hcl, next to the nested stack’s terragrunt.stack.hcl, and adds them to that stack. A block with the same name as a unit or stack block in the nested stack replaces it, including its path and autoinclude.

terragrunt.stack.hcl
stack "networking" {
source = "../catalog/stacks/networking"
path = "networking"
autoinclude {
unit "extra" {
source = "${get_repo_root()}/catalog/units/extra"
path = "extra"
}
}
}
  • terragrunt.stack.hcl
  • Directory.terragrunt-stack
    • Directorynetworking
      • terragrunt.stack.hcl
      • terragrunt.autoinclude.stack.hcl adds the extra unit

Terragrunt rejects a dependency block here, because a stack has no dependencies of its own. Declare it in the autoinclude block of the unit that needs it.

Limitations

  • unit.<name>.path and stack.<name>.path only see components declared in the same stack file, so an autoinclude block can’t refer to a unit inside a nested stack. Depend on the whole stack, or pass the component’s path down to the nested stack through values.
  • A unit or stack block added by a stack-level autoinclude can have its own autoinclude block. Terragrunt resolves it in the next generation pass, and its unit.<name>.path and stack.<name>.path references resolve in the stack file that declares it.
  • Only HCL stack files support autoinclude. A JSON stack file that declares one fails generation.

Examples

The autoinclude block adds an errors block to the app unit, so the unit retries transient errors without a change to its source in the catalog.

terragrunt.stack.hcl
unit "app" {
source = "../catalog/units/app"
path = "app"
autoinclude {
errors {
retry "transient" {
retryable_errors = [".*Error: transient.*"]
max_attempts = 3
sleep_interval_sec = 5
}
}
}
}