๐๏ธ Performance Improvements
run_cmd and Git repo-root results memoized without the Provider Cache Server
Within a single command, repeated run_cmd(...) calls and repeated Git repo-root lookups across units used to share their cached results only when the Provider Cache Server was running. Commands invoked without --provider-cache (the common case for find, list, and run --all against estates that do not need provider caching) re-evaluated each run_cmd and re-shelled to git rev-parse --show-toplevel for every unit.
Both caches are now active for every command, so identical run_cmd arguments and repeated repo-root lookups are reused across units regardless of whether the Provider Cache Server is enabled.
fast-copy strict control
With the new fast-copy strict control enabled, Terragrunt compiles each include_in_copy and exclude_from_copy pattern once and evaluates it inline during a single copy walk. This avoids re-walking subdirectories for every pattern, which should result in noticeable speed improvements for large source modules.
terragrunt run plan --strict-control fast-copyThe new matcher does not collapse ** to zero path segments when a neighbor is a wildcard, so a/**/*.tf matches a/sub/main.tf but not a/main.tf. Patterns that relied on the old collapsing behavior should use brace alternation like {*.tf,**/*.tf} to cover both depths.
Fewer git rev-parse invocations on large estates
The get_repo_root() HCL function, the runner, and the find and list discovery commands all ask Git for the enclosing repository root. Previously, two units in the same repository each triggered their own git rev-parse --show-toplevel, even when the answer was identical. On large estates this added up to one fork per unit (and sometimes more) for a value that never changed.
A repository discovered for one working directory is now reused for any other working directory inside it, for the duration of the command. Nested repositories (a checkout vendored inside another) still resolve to their own root.
๐ Bug Fixes
--auth-provider-cmd no longer runs once per dependency cache directory
Resolving dependency outputs ran the configured --auth-provider-cmd again from inside each .terragrunt-cache working directory, on top of the call already made for the unit.
Terragrunt now reuses the credentials already obtained for the dependency when reading outputs from a cached working directory, so --auth-provider-cmd is invoked once per dependency instead of twice.
Fixed exclude block being dropped when defined only in an included parent
A unit that pulled in an exclude block from an include that did not declare its own exclude block saw the include's exclude configurations ignored.
Included exclude blocks now get properly merged into unit configurations.
Reported in #5089. Thanks to @HeikoNeblung for contributing this fix!
Fixed terragrunt find --include failing on relative include paths
Running terragrunt find --include against units whose include blocks reference parent configs with relative paths (../root.hcl, ./common.hcl, bare filenames, etc.) emitted errors like Rel: can't make ../root.hcl relative to /abs/working-dir and dropped those entries from the output.
Relative include paths are now resolved against the unit's directory before being made relative to the working directory, matching how the rest of Terragrunt interprets the path attribute on an include block.
find and list no longer hard-fail when a path cannot be made relative to its base. The condition is logged as a warning and the path is emitted as-is, so output stays complete and the command exits zero.
Tolerate non-JSON warnings in tofu/terraform output -json
Resolving dependency outputs no longer fails when the underlying tofu/terraform output -json invocation prints a deprecation warning to stdout alongside the JSON payload. Terraform 1.15.0 introduced a backend deprecation warning for the S3 dynamodb_table parameter that is emitted on stdout after the JSON object, which broke parsing with errors like invalid character 'W' after top-level value and the misleading downstream message There is no variable named "dependency".
Terragrunt now isolates the first JSON object in the captured stdout, so leading log lines (for example, the long-standing AWS Client Side Monitoring Enabling CSM line) and trailing warning blocks are both ignored when reading dependency outputs.
Resolves #6001. Thanks to @jpke for contributing this fix!
get_repo_root() returns OS-native separators on Windows
git rev-parse --show-toplevel always emits forward-slash paths, even on Windows. Terragrunt returned that string unchanged from get_repo_root(), so configurations that compared the result against path/filepath-style paths or fed it back into helpers expecting OS-native separators saw spurious mismatches and broken joins on Windows.
The output is now normalized to OS-native separators before being returned, so get_repo_root() produces C:\repo\path on Windows and /repo/path on Linux and macOS.
Reported in #5976.
Hardened module manifest handling
Terragrunt now bounds .terragrunt-module-manifest cleanup to the manifest's own folder, skips paths with symlinked parents, and removes invalid manifests after reading any valid entries. Existing manifests keep the same gob format.
Provider Cache Server now supports custom host blocks
Running terragrunt with the Provider Cache Server enabled against a private registry declared via a host block in .terraformrc (or a file referenced by TF_CLI_CONFIG_FILE) failed with errors such as provider registry.opentofu.org/<org>/<provider> was not found, because the cache server proxy did not recognize the custom registry and rewrote requests to registry.opentofu.org.
Terragrunt now registers each custom host block with the cache server, seeds its service discovery from the services map so registries that do not serve .well-known/terraform.json still work, and forwards OPENTOFU_NETRC_* / TF_TOKEN_* credentials so authenticated registries continue to authenticate through the proxy.
# .terraformrc
host "registry.example.com" {
services = {
"providers.v1" = "https://registry.example.com/repository/terraform-hosted/v1/providers/"
}
}Resolves #5916. Thanks to @elkh510 for contributing this fix!
๐งช Experiments Updated
stack-dependencies โ Stricter validation and clearer parse errors for autoinclude
Malformed configuration inside an autoinclude block previously produced misleading messages or, in some cases, was silently ignored during stack discovery.
Two changes tighten this up:
- A
dependencyblock insideautoincludemust declare exactly one label. Zero labels (dependency {}) and multiple labels (dependency "a" "b" {}) are now rejected at parse time with a diagnostic that points at the offending block. - Parse failures encountered while expanding
autoincludefiles duringstack generate,find, andlistare surfaced with the underlying HCL diagnostic instead of being swallowed or remapped to a generic discovery error.
Reported in #5980.
cas โ Local paths supported as stack component sources
CAS-backed stack generation now accepts a local filesystem path as the source of a consumer stack or unit block, in addition to a remote Git URL. Terragrunt copies the referenced directory into a temporary directory, computes a content-addressed root hash over the copy, and applies the same update_source_with_cas rewriting as the remote flow. The original directory is left untouched.
# live/terragrunt.stack.hcl
stack "service" {
source = "../catalog//stacks/service"
path = "service"
}This makes a catalog usable against a local checkout under the same update_source_with_cas = true attributes that already work for Git URLs, which is helpful when iterating on a catalog before tagging a release.
See the CAS documentation and Explicit Stacks: Local catalog sources for details.
catalog-redesign โ Units and stacks, scaffolded values, and key-binding cleanup
The redesigned terragrunt catalog TUI gains two new component kinds, a guided scaffolding flow for placing them, and a small key-binding cleanup.
Units and stacks join modules and templates
Catalog discovery now classifies units (directories containing a terragrunt.hcl) and stacks (directories containing a terragrunt.stack.hcl) as first-class component kinds, alongside OpenTofu/Terraform modules and boilerplate templates. The list view picks them up automatically and they appear under their own tabs; press tab and shift+tab to cycle.
When more than one classification could apply to the same directory (for example, a stack directory that also contains a unit), Terragrunt resolves it to a single kind under a fixed precedence: template, stack, unit, module.
Copy and scaffolded values
Selecting a unit or stack from the catalog now offers a copy action that materializes the component into your working directory. Terragrunt walks the copied component for values.<name> references and, if it finds any, writes a sibling terragrunt.values.hcl stub. Names referenced outside a try(...) are listed as required with a "TODO" placeholder; names referenced through a try(...) are listed as optional, pre-populated with the literal default from the fallback. An existing terragrunt.values.hcl is left alone.
After the TUI exits, Terragrunt prints a short callout pointing at the directory it wrote to and any follow-up command you need to run, instead of leaving you to find the new directory yourself.
Catalog key bindings
The ctrl+j binding on the catalog list has been removed in favor of enter alone for choosing a focused entry, and dropped from the navigation set used while filtering. The mini help footer is updated to match.
stack-dependencies - Separate filenames for unit vs stack autoincludes
Generated autoinclude files now use distinct filenames depending on the component kind, so tooling (LSP, read_terragrunt_config(), indexers) can identify a file's purpose from its name alone:
- Unit-level autoincludes continue to be written as
terragrunt.autoinclude.hcl. - Stack-level autoincludes (autoinclude blocks declared inside a
stack { ... }) are now written asterragrunt.autoinclude.stack.hcl. The.stack.hclsuffix mirrorsterragrunt.stack.hcl, matching the convention used elsewhere for stack files.
# terragrunt.stack.hcl
unit "app" {
source = "../catalog/units/app"
path = "app"
autoinclude {
# Generated as: .terragrunt-stack/app/terragrunt.autoinclude.hcl
dependency "vpc" { config_path = unit.vpc.path }
}
}
stack "networking" {
source = "../catalog/stacks/networking"
path = "networking"
autoinclude {
# Generated as: .terragrunt-stack/networking/terragrunt.autoinclude.stack.hcl
dependency "shared" { config_path = unit.shared.path }
}
}This change implements the naming convention proposed in the Stack Dependencies RFC so configurations for units and stacks always live in files whose names clearly indicate their purpose.
To learn more, see the experiment documentation.
stack-dependencies โ Nested stack paths and discovery integration
The stack-dependencies experiment gains two improvements: nested stack path references at arbitrary depth, and integration with the find and list discovery commands.
Nested stack path references
stack.<name>.<nested_stack>.path now resolves at arbitrary nesting depth. Previously, only units within a stack were reachable via stack.<name>.<unit_name>.path; nested stacks are now first-class references too.
# terragrunt.stack.hcl
stack "infra" {
source = "../catalog/stacks/infra"
path = "infra"
}
unit "app" {
source = "../catalog/units/app"
path = "app"
autoinclude {
dependency "deep" {
# infra contains a nested "deep" stack; reference it directly.
config_path = stack.infra.deep.path
}
inputs = {
val = dependency.deep.outputs.val
}
}
}Discovery commands surface stack dependencies
The terragrunt find and terragrunt list discovery commands now reflect stack dependencies generated by the autoinclude block. The DAG output correctly orders units by their autoinclude dependencies and shows dependency relationships in JSON, tree, and long formats.
# JSON output includes dependency relationships from autoinclude
$ terragrunt find --json --dag --dependencies --experiment stack-dependencies
# Long list format shows a Dependencies column
$ terragrunt list --long --dependencies --dag --experiment stack-dependencies
# Tree format visualizes the dependency hierarchy
$ terragrunt list --tree --dag --experiment stack-dependenciesMulti-level dependency trees (for example, A โ B,C where B โ D,E) are ordered correctly in DAG mode: leaf units appear first, parents appear after all their dependencies.
To learn more, see the experiment documentation.
Cache and plugin directories follow platform conventions
Terragrunt's global cache directory now resolves to the platform's user cache location instead of a hard-coded ~/.cache/terragrunt. On Linux this honors XDG_CACHE_HOME (still ~/.cache/terragrunt by default), on macOS it resolves to ~/Library/Caches/terragrunt, and on Windows it resolves under %LocalAppData%. The CAS content store, the auto provider cache, and the IaC engine plugin directory all move with it.
Existing caches at the previous locations are not migrated. They become orphaned and continue to consume disk space until removed.
Consider deleting the old paths to reclaim that space if you are on macOS or Windows, or have configured a custom XDG_CACHE_HOME:
# CAS store and engine plugins under the legacy ~/.cache layout
rm -rf ~/.cache/terragruntWhat's Changed
- feat: stack dependencies in find and dag by @denis256 in #5945
- feat: Adding support for units and stacks in catalog by @yhakbar in #5971
- feat: Adding support for local paths in CAS by @yhakbar in #5933
- feat: updated name for stack depednencies by @denis256 in #6018
- fix: manifest handling improvements by @denis256 in #6032
- fix: Removing extra
--auth-provider-cmdcall by @yhakbar in #6045 - fix: Use
FromSlashon return ofgit rev-parse --show-toplevelby @yhakbar in #5987 - fix: Cleaning up #5232 by @yhakbar in #6009
- fix: better errors reporting form autoincludes by @denis256 in #5985
- fix: Dropping references to
ctrl-\+jin the catalog key bindings by @yhakbar in #6007 - fix: Ensuring relativization is safer by @yhakbar in #6025
- fix: tolerate non-JSON warnings in tofu/terraform output -json (#6001) by @jpke in #6029
- fix: support custom host blocks in Provider Cache Server by @elkh510 in #5917
- perf: Memoizing
get_repo_root()better by @yhakbar in #5989 - perf: Moving context cache construction earlier by @yhakbar in #6019
- docs: Adding a
Pull Requestssection to the changelog by @yhakbar in #5982 - docs: Update buttons on nav by @karlcarstensen in #6000
- docs: Calling out update for existing cache locations in
v1.0.4by @yhakbar in #6005 - docs: Cleaning up changelog for
v1.0.4by @yhakbar in #6050 - docs: Documenting #5917 by @yhakbar in #6044
- docs: Optimizing SEO a bit by @yhakbar in #5990
- docs: Updating terminology to modernize it a bit by @yhakbar in #6016
- chore: Supporting immutable releases by @yhakbar in #5905
- chore: Cleaning up other scripts with shellcheck and shfmt by @yhakbar in #5983
- chore: Add
fast-copystrict control by @yhakbar in #5966 - chore: Expanding
lllcoverage toworktreesby @yhakbar in #5861 - chore: Expanding
lllcoverage toos-execby @yhakbar in #5862 - chore: Expanding
lllcoverage torunner-credsby @yhakbar in #5865 - chore: Expanding
lllcoverage totflintby @yhakbar in #5866 - chore: Expanding
lllcoverage toqueueby @yhakbar in #5867 - chore: Addressing review feedback on #5989 by @yhakbar in #5991
- chore: Integrate
vexecinto engine by @yhakbar in #5957 - chore: Moving to XDG-aware paths by @yhakbar in #5941
- chore: Optimizing catalog performance by @yhakbar in #5973
- chore: Adding checkbox for changelog updates by @yhakbar in #6012
- chore: Integrating
vexecintoCommandby @yhakbar in #6004 - chore: Addressing #6019 feedback by @yhakbar in #6023
- chore: Addressing test flakes by @yhakbar in #6028
- chore: Adding thank you to @jpke for fix in #6029 by @yhakbar in #6035
- chore: Dropping insignificant OpenTelemetry traces by @yhakbar in #6034
- chore: Adding better symlinks experiment tests by @yhakbar in #6038
New Contributors
Full Changelog: v1.0.3...v1.0.4