NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
Go modules · #367 by repository stars
Last release 4 days ago
02 Oct 2026
Ships on a steady schedule
a new release about every 8 days
Rarely documented
notes for 10 of the last 60 stable releases
Nothing withdrawn
no release was ever pulled
10 years old
1731 releases · first in 2016
Nothing published for this version
Nothing published for this version
Nothing published for this version
One column per quarter.
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
chore: Fixing usage of deprecated aws sdk by @yhakbar in #6600
Unsupported attribute errors for values.* inputs that autoinclude overridesA unit input referencing a values.* key that the unit's values file doesn't define no longer fails with Unsupported attribute when an autoinclude block supplies that input. The autoinclude value is applied as intended.
# stacks/terragrunt.stack.hcl
unit "subnet" {
source = "../units/subnet"
path = "subnet"
autoinclude {
dependency "vpc" {
config_path = unit.vpc.path
mock_outputs = { vpc_id = "mock" }
}
inputs = {
vpc_id = dependency.vpc.outputs.vpc_id
}
}
values = {
cidr_block = "10.0.0.0/24"
}
}# units/subnet/terragrunt.hcl
inputs = {
vpc_id = values.vpc_id # supplied by autoinclude, not the values file
cidr_block = values.cidr_block # still resolves from values file
}overwrite_terragrunt and remove_terragrunt on files with no trailing newlinegenerate blocks using if_exists = "overwrite_terragrunt" or if_disabled = "remove_terragrunt" failed to properly handle existing files when the file at the target path had no newline after its first line, empty files included.
Terragrunt now properly handles files like this, so a file carrying the Terragrunt signature is overwritten or removed as configured, and a file without it produces the usual error naming the path Terragrunt would not touch.
mock_outputs apply when the state bucket doesn't exist yetWhen reading a dependency's outputs directly from remote state (--dependency-fetch-output-from-state), Terragrunt fell back to mock_outputs only when the state object was missing, not when the S3 bucket itself didn't exist. A dependency on an environment that hadn't been bootstrapped yet would fail instead of using its mocks.
A missing bucket is now treated the same as a missing state object, so commands like plan and validate can resolve mocks before the dependency's backend has been created.
include_in_copyWith the fast-copy strict control enabled, a hidden directory that Terragrunt copied due to include_in_copy matching something within it took the permissions of the first file generated within it, instead of the permissions it had in the source.
Those directories now keep their source permissions, matching the copy Terragrunt performs with the control disabled.
When a --filter query began with a negation, Terragrunt treated the whole query as an exclusion. The expressions chained after the negation stopped restricting the selection and only narrowed what got subtracted, so components matching none of them came back in the results. Those expressions are now applied.
$ terragrunt list
bar baz foo$ terragrunt list --filter '!name=foo | name=bar'
bar baz foo$ terragrunt list --filter '!name=foo | name=bar'
barThis follows the left-to-right refinement that | has everywhere else: each expression narrows what the one before it selected. A query is only treated as an exclusion when every one of its expressions is negated, such as '!name=foo' or '!name=foo | !name=bar'.
See Combining Expressions for how negation, intersection and union interact.
With the provider cache server enabled via --provider-cache, a race let the server start responding to requests before it had finished preparing the directories it caches into. A provider requested in that window had its archive and lock file written relative to the working directory instead of into the cache, leaving zip files behind in your project.
That race condition has been fixed. Providers now always download into the cache directory.
A race condition in the logic used to synchronize provider downloads meant that two Terragrunt runs on the same machine could interfere with each other while caching the same provider. Each run staged its downloads at the same path, so a run that finished first could delete an archive another run was still unpacking, failing that run with failed to open zip archive.
That race condition is now fixed. Two runs can cache the same provider at the same time.
providers lockThe space-delimited form, providers lock -platform linux_amd64, now reaches OpenTofu and Terraform intact. Previously it was the attached form, -platform=linux_amd64, that worked: given the value as a separate argument, Terragrunt moved it to the end of the command, where it was read as a provider address and the run failed with Invalid provider type "linux_amd64".
-fs-mirror and -net-mirror were moved the same way, and now keep their values too.
With --provider-cache enabled, platforms are also split correctly across the per-platform providers lock runs used to warm the cache.
scaffold on units and stacksterragrunt scaffold read every source as an OpenTofu/Terraform module. Given a unit or a stack, which are Terragrunt configurations rather than OpenTofu/Terraform modules, it exited successfully having written an invalid terragrunt.hcl file.
Units and stacks are now scaffolded the way the Catalog TUI scaffolds them: their files are copied into the working directory for you to edit in place, along with a terragrunt.values.hcl listing every values.* reference the configuration makes.
terragrunt scaffold 'github.com/gruntwork-io/terragrunt-scale-catalog//units/aws/oidc/iam-oidc-role'Copying refuses to overwrite: a file that would land on an existing path stops the command before anything is written. Modules and templates are unaffected and are still scaffolded from their variables.
See Scaffold for what gets copied and how the values file is filled in.
A run that asks for confirmation more than once, such as terragrunt backend delete prompting for both the lock table entry and the state object, used to read only the first answer when the answers were piped in rather than typed. The remaining answers were discarded while reading ahead, and the next prompt failed with an end-of-input error. Every prompt in a run now reads from the same input, so piping yes for each one works.
mock_outputs with --dependency-fetch-output-from-stateA dependency block that reads outputs from a stack (its config_path points at a terragrunt.stack.hcl directory) used to fail when a unit in that stack had no state yet, even when the dependency declared mock_outputs. This blocked commands like plan and validate against a stack that hadn't been applied.
Such a dependency now falls back to mock_outputs for the units that have no state yet. In a partially applied stack, applied units resolve to their real outputs while the rest use their mocks.
Mocks for a stack dependency are keyed by unit name, so mock_outputs has to be a map or object. Declaring it as any other type now reports that directly, instead of leaving the units it can't cover out of the stack outputs.
--config= being ignored by the tflint hookThe built-in tflint hook reads the configuration file out of the arguments you give it, then uses that path for tflint init and for the lint run. It only recognized the space-separated --config <path> spelling, so a hook written as:
before_hook "tflint" {
commands = ["plan"]
execute = ["tflint", "--config=custom.tflint.hcl"]
}was treated as though no configuration file had been named at all. Terragrunt searched the unit directory and its parents for a .tflint.hcl file instead, and either failed with a config-not-found error or ran tflint init against whatever unrelated configuration the search turned up. Terragrunt now recognizes --config <path>, --config=<path>, -c <path>, and -c=<path>.
The hook also builds --var arguments from the unit's inputs and from TF_VAR_ entries in extra_arguments blocks. Those arguments came out in a different order on every run, which made the logged command line, and anything comparing it between runs, needlessly unstable. They are now ordered by variable name.
block-iteration experiment reserves the expansion blockThe block-iteration experiment has been added as the gate for iterating a dependency, unit, or stack block over a count or for_each, declared through a nested expansion block, along with an enabled attribute on unit and stack blocks.
In this release the flag is reserved only, and enabling it has no behavioral effect. Writing an expansion block without the experiment now reports an error naming the flag, rather than leaving the block to be silently discarded:
the unit "app" block in /path/to/terragrunt.stack.hcl uses an expansion block, which requires the 'block-iteration' experiment; enable it with --experiment block-iteration
Track progress and share feedback in #4504.
bounded-discovery — Added a directory boundary for graph traversalFilter expressions that traverse the dependency graph reach beyond the working directory: dependents (--filter '...{unit}') by walking up to the Git repository root, dependencies (--filter '{unit}...') by following declared paths. Either way, Terragrunt reads and parses every configuration it touches. In monorepos with isolated environments, that traversal can fail or do wasted work reading sibling environments.
Enable the new bounded-discovery experiment to set a boundary for that traversal. The --discovery-boundary flag (env: TG_DISCOVERY_BOUNDARY) replaces the Git repository root as the enclosure for a whole run:
cd environments/staging
terragrunt run --all plan --experiment bounded-discovery --filter '...{vpc}' --discovery-boundary .The experiment also unlocks an inline (dir) boundary operand, which bounds a single expression and overrides the flag. It occupies the same slot as a traversal depth, so it bounds discovery by location the way a number bounds it by graph hops:
cd environments/staging
terragrunt run --all plan --experiment bounded-discovery --filter '(.)...{vpc}'Any configuration that resolves outside the boundary, whether a dependent or a dependency, is not read, parsed, or returned: find does not list it and run --all does not run it. Configurations inside the boundary are discovered as usual.
The boundary must be an existing directory, and relative paths are resolved against the working directory. Dependent traversal searches upward from the working directory, so filters that use it also need the boundary to be the working directory or one of its parents. Dependency traversal follows declared paths from the units a filter matched, so dependency-only filters accept any directory, including one below the working directory:
# From the repository root, follow app's dependencies but keep them within prod
terragrunt find --experiment bounded-discovery --filter '{./prod/app}...' --discovery-boundary ./prodReserving ( and ) for the boundary operand changes how --filter reads those characters everywhere, not only when the experiment is enabled. An expression such as --filter '1...(foo | bar)' previously matched a unit literally named (foo or bar); it is now rejected as a malformed boundary. Wrap a name or path containing parentheses in braces (e.g. --filter '{./weird(name)}') to keep it literal.
browse-tui — Added an interactive browser for your estateThe new browse-tui experiment adds the terragrunt browse command. With the experiment enabled, terragrunt browse opens a three-column Terminal User Interface (TUI) browser of your infrastructure estate: the parent directory on the left, the current directory in the middle, and a detail pane on the right showing metadata for the highlighted unit, stack, or directory. The browser opens immediately and fills in metadata as discovery completes in the background.
Enable it with --experiment browse-tui or TG_EXPERIMENT=browse-tui. See the experiment documentation for the keybindings, search, and the criteria for stabilization.
mutable-generate — Deduplicated generate block outputThe mutable-generate experiment has been added. With it enabled, the contents a generate block produces are stored in the Content Addressable Store (CAS), and the file written at path is a read-only link to that stored copy rather than a file of its own.
Since the stored copy is addressed by the hash of its contents, anything generating identical contents links to the same copy. A generate block inherited by several hundred units therefore costs one copy in .terragrunt-cache rather than several hundred.
The link is read-only because that copy is shared. Where a generated file does need to be edited in place, a new mutable attribute on the generate block gives it a writable file of its own:
generate "provider" {
path = "provider.tf"
if_exists = "overwrite"
mutable = true
contents = "..."
}Setting mutable without the experiment enabled is an error, since earlier Terragrunt versions reject the attribute. The CAS is required, so --no-cas writes generated files directly and mutable has no effect.
For details, see the experiment documentation.
optional-dependency-outputs — Added --no-dependency-outputs flag to skip dependency output resolutionAdded a --no-dependency-outputs flag that skips all dependency output resolution globally, mirroring the existing skip_outputs = true attribute on individual dependency blocks.
The feature is gated behind the optional-dependency-outputs experiment:
TG_EXPERIMENT=optional-dependency-outputs terragrunt run --no-dependency-outputs -- initUsing --no-dependency-outputs without enabling the optional-dependency-outputs experiment will return an error.
Thanks to @pjrm for contributing this feature!
catalog-format — Added reading the catalog as JSON LinesThe catalog command draws a terminal user interface, and refuses to start where there is no terminal to draw it on. With the catalog-format experiment enabled, --format=jsonl writes the same discovery to standard output instead, as one JSON object per line:
terragrunt catalog --experiment=catalog-format --format=jsonl | jq -c '{kind, title, component_source}'Entries are written as they are discovered rather than collected first, so output is readable while the remaining repositories are still loading, and a reader that stops early ends the command quietly:
terragrunt catalog --experiment=catalog-format --format=jsonl | head -5Note
Closing the pipe
In this example, the head program exits after reading in five lines, and Terragrunt detects the SIGPIPE signal from the OS, and shuts down cleanly.
Entries appear in discovery order, which interleaves the repositories being loaded and differs between runs. Every entry carries the complete body of the component's README in the doc field. Combine usage of Terragrunt with other tools like jq to drop it.
terragrunt catalog --experiment=catalog-format --format=jsonl | jq -c 'del(.doc)'Entries follow a published JSON schema. For the fields and their meanings, see Non-interactive catalog.
--format=tui is the default, and leaves the terminal user interface exactly as it was.
catalog-format — Added reading the catalog as MarkdownThe catalog-format experiment gains a second non-interactive format. Where --format=jsonl writes a record per catalog entry for a program to parse, --format=md writes one Markdown document for a person or an agent to read:
terragrunt catalog --experiment=catalog-format --format=md > catalog.mdEach entry becomes a section holding the metadata the catalog user interface shows for it, the source the component is scaffolded from, and the component's README. Sections are written as entries are discovered, so the document is readable while the remaining repositories are still loading.
READMEs are reproduced inside fenced blocks, so the headings one carries are not read as sections of the catalog document. The document closes with a table naming every component it holds and a count of what was discovered, which is how a reader tells a complete document from one that was cut short by a consumer that stopped reading.
For the fields each section carries, see Non-interactive catalog.
oci — Added OCI sources for stack units and stacksterragrunt.stack.hcl now accepts oci:// sources in unit and stack blocks, so a stack can pull its components straight from an OCI registry. Without the oci experiment enabled, such a source fails with a clear error instead of an unsupported-scheme failure.
oci — Added OpenTofu CLI-config credentials for OCI module sourcesoci:// module downloads now read OpenTofu's CLI-config credentials, so one configuration serves both OpenTofu and Terragrunt.
Terragrunt honors the oci_credentials "<registry>[/<repo-prefix>]" blocks (username and password, OAuth tokens, or a docker_credentials_helper, which like tofu may only be set on a whole registry) and the oci_default_credentials fallback helper. A TF_CLI_CONFIG_FILE or TERRAFORM_CONFIG value selects the config file outright; otherwise Terragrunt reads the first of ~/.tofurc and ~/.terraformrc that exists, and merges the *.tfrc and *.tfrc.json files in OpenTofu's config directory.
Terragrunt picks the most specific matching source across CLI config and ambient Docker config; an explicit CLI-config entry wins when both match equally. Set discover_ambient_credentials = false in the oci_default_credentials block to use CLI config only.
v1.26.5The version of Golang used to compile the Terragrunt binary has been updated from v1.26.0 to v1.26.5.
Thanks to @apoiget for contributing this upgrade!
() syntax by @yhakbar in #6365--discovery-boundary flag by @yhakbar in #6355browse by @yhakbar in #6219mutable attribute to the generate block by @yhakbar in #6563md format for catalog by @yhakbar in #6608EOF in generate blocks by @yhakbar in #6592--config= form of flags used in the tflint hook by @yhakbar in #6591providers lock -platform usage with space delimited values by @yhakbar in #6597negation | positive expression in the same query. by @yhakbar in #6598md format catalog escaping by @yhakbar in #6638evalCtx for discovery boundary by @yhakbar in #6632v1.1.3 by @yhakbar in #6669ParseFromFile by @yhakbar in #6561catalog-format experiment by @yhakbar in #6582go test ./... on a fresh clone of the repo by @yhakbar in #6553block-iteration experiment by @yhakbar in #6562TestCatalogJSONLFormatCleansUpOnEarlyExit by @yhakbar in #6613hcl fmt by @yhakbar in #6621hg usage test behind the exec build flag by @yhakbar in #6637init-cache fixture by @yhakbar in #6639NewParsingContext constructor by passing in venv as a param by @yhakbar in #6630internal/md by @yhakbar in #66401.26.5 (#6664) by @apoiget in #6666Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Scaffold straight from the catalog README view with ctrl+d
ctrl+dIn the terragrunt catalog TUI, pressing ctrl+d while reading a component's README now scaffolds it immediately, skipping the interactive form. Module and template inputs are written as # TODO placeholders, and unit/stack copies get a fully placeholder terragrunt.values.hcl. The hint bar at the bottom of the README view advertises the new key.
find_in_parent_folders()find_in_parent_folders() walks up from a unit toward the filesystem root, checking each directory for the configuration file it was asked to find. Even when the call named a file, as in find_in_parent_folders("root.hcl"), each directory along the way was also checked for the default configuration filenames. Units sharing a parent chain then repeated every check their siblings had already made.
Terragrunt now checks only the filename the call names, and reuses what it already learned about a directory for the rest of the command. Deeply nested estates benefit most, since every level between a unit and its root configuration used to be re-checked once per unit.
In micro-benchmarks, resolving the root configuration for 100 units nested eight directories deep went from 4.8ms to 0.49ms. Across the benchmarked shapes the lookups run between 7x and 10x faster, and the time saved grows with both the number of units and how deeply they sit below their root configuration.
A regression in v1.1.1 broke setups that provide static AWS credentials and configure a role via the iam_role attribute, the --iam-assume-role flag, or TG_IAM_ASSUME_ROLE.
In those setups, Terragrunt assumes the role once at the start of a run, and every later AWS call uses that role session. In v1.1.1, backend operations like bootstrapping the state bucket started performing an extra role assumption of their own. Since the run was already using the role session at that point, the role tried to assume itself, and AWS rejected the call with an AccessDenied error unless the role's trust policy happened to include the role itself.
Backend operations now reuse the role session from the start of the run, as they did before v1.1.1.
This does not affect the assume_role attribute of the remote_state block. Roles configured there are backend-specific and are still assumed on top of the supplied credentials, so the cross-account role assumption should continue to work as expected.
For units with a local source, Terragrunt decides whether the cached copy is stale by hashing the source directory. That hash previously covered every file in the directory, including hidden files and exclude_from_copy matches that are never copied into the cache. Creating or touching such a file (an editor swap file, a scratch note) changed the hash, forcing a needless re-copy and auto-init on the next run.
The hash now covers only the files a copy would deliver, honoring the default hidden-file rule along with include_in_copy and exclude_from_copy. Files that never reach the cache no longer trigger re-initialization.
width truncation of colored and multi-byte log contentThe width option in a custom log format sizes a column to a fixed number of visible characters. When the content held color codes or multi-byte characters and was longer than the column, truncation cut the raw bytes: it could slice through the middle of a color code, leaving color bleeding into the rest of the line, or split a multi-byte character into invalid output, and it dropped more visible text than the configured width.
width now measures and cuts by visible characters. Color codes are preserved intact, multi-byte characters are never split, and the column keeps exactly the requested number of visible characters.
The Provider Cache Server now hardens the download endpoint that fetches provider archives on the caller's behalf. That endpoint attaches whatever registry credentials are configured for the upstream host, and it was the only one on the server that did not require the token generated for the run, so any other process on the machine could use a running cache server to pull artifacts from a private registry with the credentials of whoever started the run.
The download URLs handed to OpenTofu and Terraform now carry a secret path segment, generated fresh each time the cache server starts and redacted from the server's own logs. Requests that omit the segment get a 404.
When a run's path shared a string prefix with the working directory without being nested under it, the run report shortened its name by shearing off the prefix mid-segment. A working directory of /repo/project alongside a run at /repo/project-staging/unit produced the name -staging/unit.
The report now shortens a path only when it is genuinely nested under the working directory. Sibling paths keep their full name.
run --allA feature block's default was recorded once per run and shared by every unit. During run --all, the first unit to be parsed set the value for a flag name, so a unit defining default = false could evaluate feature.toggle.value as true because a sibling unit was parsed first. Which unit won depended on parsing order, making the result vary between runs.
Defaults are now resolved per unit, including defaults inherited through include. Overrides passed with --feature or TG_FEATURE continue to apply to every unit in the run.
Thanks to @dhotcolorado for reporting and fixing this!
Downloading unit sources from private S3 buckets (s3::https://...) now works when EKS Pod Identity is the only credential source. Previously, the bundled aws-sdk-go v1 rejected the Pod Identity Agent endpoint (169.254.170.23) because it only allowed loopback hosts. Terragrunt now uses aws-sdk-go v1.55.6, which allows the EKS and ECS container credential endpoints.
otel-logs experiment exports logs to OpenTelemetryTerragrunt previously emitted only traces and metrics, so there was no way to ship its log output to an OpenTelemetry backend or correlate log lines with the spans of a failed run.
Enable the new otel-logs experiment to add an OpenTelemetry logs signal, configured with TG_TELEMETRY_LOGS_EXPORTER:
none - no log exporting, the default.console - write log records to the console as JSON.otlpHttp - export logs to an OpenTelemetry collector over HTTP.otlpGrpc - export logs to an OpenTelemetry collector over gRPC.TG_TELEMETRY_LOGS_EXPORTER=otlpHttp terragrunt run --all --experiment otel-logs -- applyThe OTLP exporters read the endpoint from the standard OTEL_EXPORTER_OTLP_ENDPOINT environment variable. Set TG_TELEMETRY_LOGS_EXPORTER_INSECURE_ENDPOINT=true to disable TLS when collecting locally. Records emitted while a span is active carry its trace and span IDs, so a failed unit's logs link to its span in the backend. Without the experiment enabled, the logs exporter stays inert regardless of TG_TELEMETRY_LOGS_EXPORTER.
profiling experiment adds pprof collection for Terragrunt runsEnable the new profiling experiment to collect CPU profiles, memory (heap) profiles, and goroutine profiles (stack traces of all goroutines) using CLI flags. Profiling is intended for debugging the performance of Terragrunt itself, and for exploring ways to optimize Terragrunt as an application; it will not help with improving the performance of the infrastructure Terragrunt manages.
Example:
terragrunt --experiment=profiling --profile-cpu cpu.prof --profile-mem mem.prof --profile-goroutine goroutine.prof run -- planUse --profile-dir to collect all profiles into a single directory with conventional names (terragrunt_cpu.prof, terragrunt_mem.prof, terragrunt_goroutine.prof):
terragrunt --experiment=profiling --profile-dir /tmp/profiles run --all -- planThe same behavior is available via environment variables when the profiling experiment is enabled:
TG_PROFILE_CPUTG_PROFILE_MEMTG_PROFILE_GOROUTINETG_PROFILE_DIRWhen using --profile-dir or TG_PROFILE_DIR, Terragrunt also sets TOFU_CPU_PROFILE for each unit so downstream OpenTofu processes (OpenTofu 1.11 or later) write their own CPU profiles into unit-specific subdirectories. An explicitly set TOFU_CPU_PROFILE is never overridden.
azure-backend now manages Azure Storage remote stateThe azure-backend experiment now enables functional Terragrunt support for the Azure Storage (azurerm) remote-state backend.
When the experiment is enabled, Terragrunt can bootstrap the resource group, storage account, and blob container used by remote_state { backend = "azurerm" }, detect whether the backend needs bootstrapping, converge blob versioning and soft-delete settings, delete state blobs or containers, and migrate state blobs within the same storage account.
Terragrunt-only settings such as location, the storage account SKU options, the skip_* flags, enable_soft_delete, soft_delete_retention_days, and msi_resource_id are consumed by Terragrunt and removed before it runs OpenTofu/Terraform with init -backend-config, so the underlying azurerm backend receives only keys it understands. msi_resource_id is not bootstrap-only: it also selects the managed identity used for delete and migrate.
This remains opt-in while the experiment is active:
terragrunt --experiment azure-backend run -- planThanks to @omattsson for driving this support forward.
oci - Credential helpers for OCI module sourcesoci:// module downloads now use the Docker credential helpers you already have configured, so registries like Amazon ECR authenticate automatically with no extra setup.
oci - Content-addressable caching for OCI module sourcesoci:// module sources now integrate with Content Addressable Storage. When the oci experiment is enabled, downloads are cached by their manifest digest, so a repeated fetch of the same tag or digest is served from the local store instead of re-downloaded from the registry.
Mutable tags stay correct: every fetch re-resolves the tag to its current manifest digest at download time, so re-pushing a module under the same tag invalidates the cache and pulls the new content rather than serving a stale copy. A digest-pinned source (?digest=sha256:...) skips registry resolution and keys the cache directly.
oci - Downloading modules from OCI registriesThe oci experiment now downloads source code (including OpenTofu modules) from OCI Distribution registries. When enabled, Terragrunt accepts oci:// source URLs in Terragrunt configurations (including terraform.source attributes). Specify either tag or digest; omitting both selects the latest tag. //subdir selectors are supported. Artifacts follow the same publishing contract OpenTofu 1.10 consumes natively.
Authentication covers static credentials via interim TG_TMP_OCI_* environment variables and read-only ambient discovery of Docker and containers auth files. Static credentials can be limited to one registry with TG_TMP_OCI_REGISTRY; without it, the configured token or username and password may be offered to any registry the process contacts. Credential helpers (such as ecr-login) are not invoked yet, so registries that need per-run token minting only work while an externally obtained login is present in an ambient file.
When the experiment is disabled, oci:// sources remain unsupported.
For setup steps, see the experiment documentation.
otel-logs experiment by @yhakbar in #6279TF_TOKEN_* rendering by @yhakbar in #6509/reference/hcl/blocks/ by @yhakbar in #6485version attribute by @yhakbar in #6482fd -tf -e go -x golines -w to avoid run-on lines by @yhakbar in #6484version attribute by @yhakbar in #6487cas.Venv by @yhakbar in #6488vsops by @yhakbar in #6506Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Chained role assumption for the S3 backend
When AWS credentials were supplied through --auth-provider-cmd or environment variables, Terragrunt ignored the assume_role attribute of the remote_state block for its own backend operations, such as bootstrapping the state bucket. In cross-account setups this caused access errors, even though OpenTofu/Terraform itself assumed the role correctly during runs.
Terragrunt now uses the supplied credentials as the source identity and assumes the configured role on top of them. The same applies to roles configured via the iam_role attribute or the --iam-assume-role flag, and to fetching dependency outputs directly from S3 state.
terragrunt catalogTerragrunt now creates a fresh temporary clone directory for each catalog load, rejects symlinked clone roots, and removes catalog clones when the TUI session exits.
dependency outputs for units that reference a dependency in a hook, extra_arguments, or remote_state blockResolving a unit's dependency outputs for a downstream unit no longer fails when that unit references its own dependency in:
before_hook, after_hook, or error_hookextra_arguments blockremote_state blockPreviously these raised There is no variable named "dependency" on the downstream unit, and a remote_state reference could crash Terragrunt.
Terragrunt now protects developer machines and CI runners from engine archives that expand into unexpectedly large amounts of data. If an IaC engine package is unusually large or contains too many files, Terragrunt stops processing it before it can consume excessive disk space.
--filter-allow-destroy with ...[] dependent-traversal filters no longer fails--filter-allow-destroy --filter '...[HEAD~1...HEAD]' failed with "Too many command line arguments" or hung when the deleted unit had dependents. Terragrunt now correctly plans and destroys deleted units regardless of whether dependents are included in the run.
find and list missing units inside generated stacks for Git-based filtersterragrunt find and terragrunt list with a Git-based filter (for example --filter '[HEAD^1...HEAD]') now detect units inside generated stacks. Previously they did not generate stacks in the worktrees they create for the comparison, so no unit nested in a generated stack was ever surfaced, while terragrunt run --all with the same filter targeted those units correctly.
This affected every change that lands inside a generated stack, including a modified terragrunt.stack.hcl, a change to a unit's own files, and a change to a file the stack reads via read_terragrunt_config or mark_glob_as_read.
Stacks are generated only inside the comparison worktrees; find and list still do not generate stacks in your current working directory by default.
ref values strictly as referencesTerragrunt now passes the ref from a Git module source to git strictly as a reference when downloading through content-addressable storage. Previously a source whose ref began with a git option (for example a value starting with --) could be interpreted by git as an option rather than a reference while fetching the source.
Terragrunt now terminates git option parsing before the repository and reference arguments in its fetch, clone, and ls-remote invocations, so these values can only ever be read as the repository and reference they are meant to be. Normal refs, branches, tags, and commit SHAs continue to work unchanged.
hcl validate resolves get_original_terragrunt_dir() to the discovered unitterragrunt hcl validate and terragrunt hcl validate --inputs now resolve get_original_terragrunt_dir() to each discovered unit's own directory instead of the directory the command was launched from. Previously, when the command ran from a parent directory that discovered units in subdirectories, any read_terragrunt_config() call that built a path relative to get_original_terragrunt_dir() resolved against the wrong directory and failed with "You attempted to run terragrunt in a folder that does not contain a terragrunt.hcl file", even though plan, apply, and run validate worked on the same configuration.
Both commands now set the original config path per discovered unit before parsing, matching the behavior of run and backend bootstrap, so relative paths resolve against the unit that owns them.
-lockfile=readonly during provider cachingWhen you pass -lockfile=readonly to init, Terragrunt no longer generates or updates .terraform.lock.hcl while warming the provider cache. Previously the cache step could write the lock file before OpenTofu/Terraform ran, so the read-only check always passed and silently defeated the flag.
Terragrunt now leaves the lock file untouched and lets OpenTofu/Terraform enforce it, failing when the lock file is missing or incomplete. The flag is honored whether it is supplied on the command line or through the TF_CLI_ARGS or TF_CLI_ARGS_init environment variables.
run --all no longer crashes on dependency discovery with graph filtersRunning run --all with a filter that expands a git range through the dependency graph (for example [HEAD~1...HEAD]...) could fail during dependency discovery, reporting that a component "is missing its working directory". Whether it happened depended on the size and shape of the changed unit's dependency closure, so the same filter succeeded on smaller branches and find was unaffected.
A dependency reached from several units at once could become visible to discovery before its working directory was set, so a concurrent traversal could read it before it was complete. Dependencies now have their working directory set before they become visible, so run --all behaves the same regardless of graph size.
terraform_binary respected by run --all when both tofu and terraform are on PATHrun --all ignored a unit's terraform_binary setting and fell back to the auto-detected default (OpenTofu when both binaries are on PATH). The per-unit options used to execute each unit are cloned from the stack options, whose binary path is the auto-detected default, and the configured value was never applied to them.
Each unit now honors its own terraform_binary, matching the behavior of a single run. Setting --tf-path or TG_TF_PATH still takes precedence over the config value.
When creating the state bucket failed during backend bootstrap, the reported error was a misleading NoSuchBucket from a follow-up access check, hiding the actual cause. The original creation error, such as AccessDenied, is now part of the reported message.
locals blocks in terragrunt.stack.hclFixed a bug where an empty locals {} block in a stack configuration could break stack generate.
terraform.source references a dependency outputA terraform.source that references dependency.<name>.outputs.<key> is now rejected with a message explaining that the module source must be resolvable before dependencies are evaluated.
Terragrunt resolves the source while discovering units and building the run queue, before any dependency has run, so such a source can never be satisfied. Previously it surfaced a cryptic decode error.
oci - Module sources from OCI registriesThe oci experiment has been added as the gate for downloading source code (including OpenTofu modules) from OCI Distribution registries using oci:// schema URLs in Terragrunt configurations (including terraform.source attributes). This targets the same registries OpenTofu 1.10 supports natively, such as Amazon ECR, GitHub Container Registry, Azure Container Registry, Google Artifact Registry, and self-hosted or air-gapped registries.
Enabling the experiment has no behavioral effect yet: the getter that will resolve oci:// sources is not wired into source downloading, so oci:// sources still fail to download. Functional support will land in follow-up releases, gated by this experiment.
For setup steps, see the experiment documentation.
version-attribute - Resolve registry modules from a version constraintThe version-attribute experiment has been added to gate a new version attribute on the terraform block. It holds a version constraint (such as ~> 3.3 or >= 1.0.0, < 2.0.0) for a tfr:// registry module, and Terragrunt resolves it to the highest published version that satisfies the constraint before downloading:
terraform {
source = "tfr://registry.opentofu.org/terraform-aws-modules/vpc/aws"
version = "~> 3.3"
}This brings the terraform block to parity with the version argument on OpenTofu and Terraform module blocks. The attribute applies to tfr:// sources only, and cannot be combined with an inline ?version= on the same source.
Enable it with --experiment version-attribute. For setup steps and the criteria for stabilization, see the experiment documentation.
Terragrunt now writes a terragrunt-crash-YYYYMMDDTHHMMSSZ-<pid>.log file when it crashes.
The report includes runtime details, the command line, the panic message, and the stack trace. You can conveniently share this file (after reviewing for sensitive information) to report panics if Terragrunt crashes.
version attribute on the terraform block by @yhakbar in #6475-lockfile=readonly by @yhakbar in #6358--filter-allow-destroy with graph + Git expression combo by @yhakbar in #6322find/list by @yhakbar in #6362terraform_binary from being ignored in run --all by @yhakbar in #6460stack generate by @yhakbar in #6470run --all with graph expression throwing on missing working dir by @yhakbar in #6474version-attribute experiment by @yhakbar in #6476version attribute experiment by @yhakbar in #6463source from version attribute error by @yhakbar in #6480Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
A stack generates a tree of units from a single terragrunt.stack.hcl file. Wiring one of those units to another used to mean defining dependency block
A stack generates a tree of units from a single terragrunt.stack.hcl file. Wiring one of those units to another used to mean defining dependency blocks in your catalog and threading dependency paths through values. Stack dependencies let you declare those relationships up front instead.
Add an autoinclude block inside a unit or stack block, and Terragrunt generates a partial configuration (a terragrunt.autoinclude.hcl file) next to the generated terragrunt.hcl or terragrunt.stack.hcl that's automatically merged into the unit or stack definition. The new unit.<name>.path and stack.<name>.path references resolve to generated paths, so you don't have to hardcode them:
# terragrunt.stack.hcl
unit "vpc" {
source = "github.com/acme/catalog//units/vpc"
path = "vpc"
}
unit "app" {
source = "github.com/acme/catalog//units/app"
path = "app"
autoinclude {
dependency "vpc" {
config_path = unit.vpc.path
}
inputs = {
vpc_id = dependency.vpc.outputs.vpc_id
}
}
}Anything that's valid in a unit configuration is valid in its autoinclude block, so you can also patch catalog units with configuration they don't ship with, like retry rules:
# terragrunt.stack.hcl
unit "app" {
source = "github.com/acme/catalog//units/app"
path = "app"
autoinclude {
errors {
retry "transient_errors" {
retryable_errors = [".*Error: transient network issue.*"]
max_attempts = 3
sleep_interval_sec = 5
}
}
}
}The same works for nested stacks: an autoinclude block inside a stack block patches the generated terragrunt.stack.hcl, so you can, for example, add an extra unit to one environment without forking the stack in your catalog.
Stack configurations also gained two capabilities along the way:
include blocks now work in terragrunt.stack.hcl files, so shared stack configuration can live in a parent folder.dependency blocks can target stack directories, and the run queue expands them to the units inside. Note that this relationship only goes one way: units can depend on stacks, but stacks cannot depend on stacks or units.See the stacks documentation for the full reference. Previously gated behind the stack-dependencies experiment, all of this is now enabled by default.
The Content Addressable Store (CAS) deduplicates source downloads across configurations. It addresses repositories and modules by their content, stores them locally, and serves later requests from that local store instead of repeating the fetch. This speeds up catalog cloning, OpenTofu/Terraform source fetching, and stack generation, and identical files occupy disk space once regardless of how many configurations use them.
The CAS is no longer limited to Git. It also deduplicates HTTP, Amazon S3, Google Cloud Storage, Mercurial, and SMB sources, along with OpenTofu/Terraform registry sources fetched via tfr://. See supported sources for how each one resolves and deduplicates content.
CAS is enabled by default. Use the --no-cas flag (or TG_NO_CAS=true) to opt out of it for a run:
terragrunt run --all --no-cas -- planTwo new attributes give you finer control, and both default to off:
update_source_with_cas makes a generated stack self-contained. Set it on a unit, stack, or terraform block with a relative source, and terragrunt stack generate rewrites that source into a content-addressed cas:: reference, so the generated tree no longer depends on the surrounding repository layout. Catalog authors can keep relative paths in their sources and still ship a portable, reproducible stack:
# stacks/networking/terragrunt.stack.hcl
unit "vpc" {
source = "../..//units/vpc"
path = "vpc"
update_source_with_cas = true
}After terragrunt stack generate, the relative path is replaced by a reference to the exact tree the CAS stored:
# Generated output
unit "vpc" {
source = "cas::sha1:f39ea0ebf891c9954c89d07b73b487ff938ef08b"
path = "vpc"
update_source_with_cas = true
}mutable controls how the CAS places fetched content on disk. By default, the CAS hard links files from its shared store into .terragrunt-cache and marks them read-only, which is fast and uses no extra space, but means the files can't be edited in place. Set mutable = true on a terraform block to copy the content instead, making the working tree safe to edit at the cost of extra I/O and disk space:
# units/vpc/terragrunt.hcl
terraform {
source = "github.com/acme/catalog//modules/vpc"
mutable = true
}Previously gated behind the cas experiment, the CAS no longer requires --experiment cas.
terragrunt catalogThe catalog command has been redesigned. It now starts without any configuration, discovers components across your catalog repositories in the background, and streams them into the TUI as they're found.
Discovery is no longer limited to a modules/ directory; components can live anywhere in a catalog repository. To control what gets discovered, add a .terragrunt-catalog-ignore file with .gitignore-style globs for the paths you want filtered out.
Components in the TUI now carry metadata to help you navigate a large catalog: each one shows a kind label (template, stack, unit, or module) and optional tags defined in the front-matter of its README.md. From the component list, press s to open a new screen that interactively collects the values used to scaffold the component into your repository.
Previously gated behind the catalog-redesign experiment, the redesigned catalog is now the default terragrunt catalog experience.
Terragrunt can select units by the files they read, which is the basis of change-based runs in CI. Previously, pointing a unit's terraform block at a local directory didn't mark the files inside that directory as read, so a change to the module wouldn't select the unit.
When a unit's source is a local module, Terragrunt now records the module's *.tf, *.tf.json, *.hcl, *.tofu, and *.tofu.json files as read by that unit, so --filter 'reading=<path>' and --queue-include-units-reading select the unit when a module file changes:
terragrunt run --all --filter 'reading=./modules/vpc/main.tf' -- planFor files that reading detection doesn't track on its own, the new mark_glob_as_read() HCL function expands a glob and marks every matching file as read in one call:
locals {
configs = mark_glob_as_read("${get_terragrunt_dir()}/config/{*.yaml,**/*.yaml}")
}Existing pipelines built on --queue-include-units-reading or reading= filters may select more units than before, because changes to local module files now count as reads. Previously gated behind the mark-many-as-read experiment, these behaviors no longer require --experiment mark-many-as-read.
--no-discovery-auth-provider-cmdBy default, Terragrunt runs your --auth-provider-cmd once for every unit it discovers, so HCL functions that need credentials resolve correctly during parsing. In a large repository, that can mean hundreds of invocations before any unit runs, which can dominate wall-clock time on change-based runs.
The --no-discovery-auth-provider-cmd flag (env: TG_NO_DISCOVERY_AUTH_PROVIDER_CMD) skips those invocations during the discovery phase, leaving auth to run only for the units that actually execute:
terragrunt run --all \
--no-discovery-auth-provider-cmd \
--queue-include-units-reading=./changed-file.txt \
-- planWarning
Use this only when you know parsing resolves without credentials. Units whose configuration depends on values from --auth-provider-cmd during discovery (for example, via get_aws_account_id()) will fail to parse when the flag is set.
Previously gated behind the opt-out-auth experiment, the flag now works without --experiment opt-out-auth.
Before a run --all, Terragrunt lists the units it's about to run. That list now renders as a dependency tree by default instead of a flat list, with units nested under their dependencies, so the run order and the relationships between units are visible before anything executes:
The following units will be run, starting with dependencies and then their dependents:
.
├── monitoring
╰── vpc
╰── database
╰── backend-app
The header adapts to direction: dependencies come before dependents on apply, and the order reverses on destroy.
Previously gated behind the dag-queue-display experiment, the tree display no longer requires --experiment dag-queue-display.
terragrunt stack generate --filter './my-stack | type=stack' generates only the selected
stack, not the nested stacks it contains, which can be surprising for a stack of stacks.
When a non-glob | type=stack filter leaves a stack's nested stacks ungenerated, Terragrunt
now prints a tip showing how to generate them too, for example
--filter './my-stack | type=stack' --filter './my-stack/** | type=stack'.
permission denied when generated files overwrite CAS-materialized filesWith the CAS enabled, Terragrunt fetches sources as read-only files. Writing a generated file over one of them no longer fails with permission denied:
generate blocks with if_exists = "overwrite", when the module ships the target file (for example, its own versions.tf).terragrunt.values.hcl, when the unit or stack source already contains one.terragrunt.autoinclude.hcl, when the unit or stack source already contains one..terraform.lock.hcl, when the provider cache server updates a committed lock file during init -upgrade.In each case, the read-only file is replaced with a writable one, and the shared CAS store is never modified.
permission denied when CAS fetches a git source across filesystemsWith the CAS enabled, fetching a git:: source could fail with permission denied on .git/HEAD or .git/config, sending Terragrunt back to the standard getter. It happened when the CAS store and the module's working directory sit on different filesystems, so the files are copied rather than hard-linked, and a read-only leftover from an interrupted run was in the way. Terragrunt now recovers from the leftover and completes the fetch.
update_source_with_cas on a terraform block when CAS is disabledterragrunt stack generate --no-cas now fails when a generated unit's terraform block sets update_source_with_cas = true, instead of silently emitting the unit with its relative source unchanged. The relative source has no meaning once CAS is disabled, so the generated unit could not resolve its module. This matches the existing behavior for the same attribute on unit and stack blocks, and for a run invoked with --no-cas.
extra_arguments env vars when resolving dependency outputsResolving a dependency block's outputs now applies the env_vars from the unit's terraform extra_arguments blocks whose commands include output.
dependency outputs for units whose before_hook references a dependencyResolving a unit's dependency outputs no longer evaluates that unit's terraform hooks, so a before_hook (or after_hook) that interpolates ${dependency.<name>.outputs.<key>} no longer fails downstream units with There is no variable named "dependency". Dependency output resolution still applies the unit's extra_arguments env_vars and source.
Git-based filters (for example terragrunt run --all --filter '[HEAD^1...HEAD]' -- plan) now select units
that read an added or deleted file through mark_glob_as_read, even when that file lives outside the unit's
own directory. Previously only modified files outside a unit reached those units; adding or deleting a file
the glob matched left the reading unit out of the run, so its real config change was skipped. Added files are
matched against the newer reference, and deleted files against the older one where the file still exists.
mark_glob_as_read constrains its walk to a boundarymark_glob_as_read now confines glob expansion to a boundary directory. By default the boundary is the enclosing Git repository root; outside a Git repository it is unset. A pattern whose walk would begin outside the boundary returns an error instead of expanding.
This bounds patterns that resolve higher than intended. For example, "${local.dir}/{*.yaml}" becomes /{*.yaml} when local.dir is empty, which previously walked the entire filesystem. A ? : conditional does not prevent this, because HCL evaluates both branches of a conditional before selecting one. Wrapping the call in try lets the error fall back to a default:
locals {
files = sort(try(mark_glob_as_read("${local.dir}/{*.yaml,*.yml,*.json}"), []))
}Pass a leading --terragrunt-boundary argument to set the boundary explicitly, for example to scope the walk to a subdirectory or to widen it to the filesystem root:
locals {
scoped = mark_glob_as_read("--terragrunt-boundary=/etc/terragrunt", "/etc/terragrunt/{*.yaml}")
all = mark_glob_as_read("--terragrunt-boundary=/", "/{*.yaml}")
}terragrunt scaffold now reads input variables from the module directory itself, matching what OpenTofu and Terraform load for a root module. Previously it scanned subdirectories too, so variable blocks defined in nested modules or examples leaked into the scaffolded inputs even though the module never exposes them.
terragrunt stack generate now resolves interpolated object keys in autoinclude blocks (for example
{ "${local.prefix}_key" = ... }), even when the value references dependency.*. Previously the generated
unit kept the key verbatim, leaking a stack-only reference that is not valid in the unit scope.
autoinclude templatesterragrunt stack generate no longer panics when an autoinclude template interpolates a non-string literal (for example "${0}" or "${true}") alongside a dependency.* reference. The interpolated literal is now rendered to its string form (${0} becomes 0) and the dependency reference is preserved for the unit.
autoinclude dependencies on a stack directoryrun --all no longer fails with "does not contain a terragrunt.hcl file" when an autoinclude dependency points at a stack directory (one holding terragrunt.stack.hcl) and the unit is reached transitively through another unit. The dependency cycle check now skips a target with no unit config, matching the direct dependency case.
The following experiments graduated to general availability in this release, and the features they gated are now enabled by default:
stack-dependenciescascatalog-redesignmark-many-as-readopt-out-authdag-queue-displayEach feature is described in the New Features section above.
The corresponding --experiment flags (and TG_EXPERIMENT values) are no longer needed. Passing one still works, but emits a warning about the completed experiment, so you can drop it at your convenience.
Thank you to everyone who ran these experiments early and filed the feedback that got them here.
Starting with this release, Terragrunt releases are published as immutable releases on GitHub. Once a release is published, its tag and assets can no longer be modified or deleted, so the binary you download is guaranteed to be the same binary that was uploaded when the release was published.
See the releases process documentation for details, and Verifying releases with the GitHub CLI for how to check a download against the release attestation.
The install script now checks downloaded release assets against the release attestation that ships with immutable releases. For releases starting with v1.1.0, when an authenticated GitHub CLI (v2.81.0 or later) is available, the script verifies the checksums file and the binary against the attestation before installing, and aborts if either does not match the published release. The check is skipped with a warning when gh is unavailable, too old, or unauthenticated. Use --no-verify-attestation to opt out.
update_source_with_cas integration with --no-cas by @yhakbar in #6363--terragrunt-boundary to mark_glob_as_read by @yhakbar in #6351--parallelism tweaking considerations better by @yhakbar in #6313v1.1.0 changelog polish by @yhakbar in #6333mark-many-as-read experiment by @yhakbar in #6310cas experiment by @yhakbar in #6254dag-queue-display experiment by @yhakbar in #6320opt-out-auth experiment by @yhakbar in #6321go-git by @yhakbar in #6325catalog-redesign experiment by @yhakbar in #6271lll by @yhakbar in #6377TestWindowsTflintIsInvoked by @yhakbar in #6382lll #2 by @yhakbar in #6385go fix ./... by @yhakbar in #6398render log to a debug by @yhakbar in #6429Nothing published for this version
Nothing published for this version
Nothing published for this version
This is the third release candidate for Terragrunt v1.1.
This is the third release candidate for Terragrunt v1.1.
It carries the same six completed experiments as v1.1.0-rc2, plus bug fixes for those experiments and improvements to how releases are published and verified.
This release completes the following experiments:
stack-dependenciescascatalog-redesignmark-many-as-readopt-out-authdag-queue-displayFuture release candidates for v1.1.0 will include bug fixes related to these experiments or other urgent bug fixes as necessary, and documentation improvements.
Please try out this release candidate in lower environments and share your feedback in the associated GitHub discussion.
Dependency output resolution: Resolving a dependency block's outputs now applies the env_vars from the unit's extra_arguments blocks whose commands include output (#6396).
Stack autoinclude: Transitive autoinclude dependencies that point at a stack directory now resolve correctly (#6389).
Scaffold variable detection: terragrunt scaffold now reads input variables from the module directory only, so variable blocks in nested modules or examples no longer leak into the scaffolded inputs (#6381).
| type=stack filter generates a stack but leaves its nested stacks ungenerated, terragrunt stack generate now prints a tip showing how to generate them too (#6387).A stack generates a tree of units from a single terragrunt.stack.hcl file. Wiring one of those units to another used to mean defining dependency blocks in your catalog and threading dependency paths through values. Stack dependencies let you declare those relationships up front instead.
Add an autoinclude block inside a unit or stack block, and Terragrunt generates a partial configuration (a terragrunt.autoinclude.hcl file) next to the generated terragrunt.hcl or terragrunt.stack.hcl that's automatically merged into the unit or stack definition. The new unit.<name>.path and stack.<name>.path references resolve to generated paths, so you don't have to hardcode them:
# terragrunt.stack.hcl
unit "vpc" {
source = "github.com/acme/catalog//units/vpc"
path = "vpc"
}
unit "app" {
source = "github.com/acme/catalog//units/app"
path = "app"
autoinclude {
dependency "vpc" {
config_path = unit.vpc.path
}
inputs = {
vpc_id = dependency.vpc.outputs.vpc_id
}
}
}Anything that's valid in a unit configuration is valid in its autoinclude block, so you can also patch catalog units with configuration they don't ship with, like retry rules:
# terragrunt.stack.hcl
unit "app" {
source = "github.com/acme/catalog//units/app"
path = "app"
autoinclude {
errors {
retry "transient_errors" {
retryable_errors = [".*Error: transient network issue.*"]
max_attempts = 3
sleep_interval_sec = 5
}
}
}
}The same works for nested stacks: an autoinclude block inside a stack block patches the generated terragrunt.stack.hcl, so you can, for example, add an extra unit to one environment without forking the stack in your catalog.
Stack configurations also gained two capabilities along the way:
include blocks now work in terragrunt.stack.hcl files, so shared stack configuration can live in a parent folder.dependency blocks can target stack directories, and the run queue expands them to the units inside. Note that this relationship only goes one way: units can depend on stacks, but stacks cannot depend on stacks or units.See the stacks documentation for the full reference. Previously gated behind the stack-dependencies experiment, all of this is now enabled by default.
The Content Addressable Store (CAS) deduplicates source downloads across configurations. It addresses repositories and modules by their content, stores them locally, and serves later requests from that local store instead of repeating the fetch. This speeds up catalog cloning, OpenTofu/Terraform source fetching, and stack generation, and identical files occupy disk space once regardless of how many configurations use them.
The CAS is no longer limited to Git. It also deduplicates HTTP, Amazon S3, Google Cloud Storage, Mercurial, and SMB sources, along with OpenTofu/Terraform registry sources fetched via tfr://. See supported sources for how each one resolves and deduplicates content.
CAS is enabled by default. Use the --no-cas flag (or TG_NO_CAS=true) to opt out of it for a run:
terragrunt run --all --no-cas -- planTwo new attributes give you finer control, and both default to off:
update_source_with_cas makes a generated stack self-contained. Set it on a unit, stack, or terraform block with a relative source, and terragrunt stack generate rewrites that source into a content-addressed cas:: reference, so the generated tree no longer depends on the surrounding repository layout. Catalog authors can keep relative paths in their sources and still ship a portable, reproducible stack:
# stacks/networking/terragrunt.stack.hcl
unit "vpc" {
source = "../..//units/vpc"
path = "vpc"
update_source_with_cas = true
}After terragrunt stack generate, the relative path is replaced by a reference to the exact tree the CAS stored:
# Generated output
unit "vpc" {
source = "cas::sha1:f39ea0ebf891c9954c89d07b73b487ff938ef08b"
path = "vpc"
update_source_with_cas = true
}mutable controls how the CAS places fetched content on disk. By default, the CAS hard links files from its shared store into .terragrunt-cache and marks them read-only, which is fast and uses no extra space, but means the files can't be edited in place. Set mutable = true on a terraform block to copy the content instead, making the working tree safe to edit at the cost of extra I/O and disk space:
# units/vpc/terragrunt.hcl
terraform {
source = "github.com/acme/catalog//modules/vpc"
mutable = true
}Previously gated behind the cas experiment, the CAS no longer requires --experiment cas.
terragrunt catalogThe catalog command has been redesigned. It now starts without any configuration, discovers components across your catalog repositories in the background, and streams them into the TUI as they're found.
Discovery is no longer limited to a modules/ directory; components can live anywhere in a catalog repository. To control what gets discovered, add a .terragrunt-catalog-ignore file with .gitignore-style globs for the paths you want filtered out.
Components in the TUI now carry metadata to help you navigate a large catalog: each one shows a kind label (template, stack, unit, or module) and optional tags defined in the front-matter of its README.md. From the component list, press s to open a new screen that interactively collects the values used to scaffold the component into your repository.
Previously gated behind the catalog-redesign experiment, the redesigned catalog is now the default terragrunt catalog experience.
Terragrunt can select units by the files they read, which is the basis of change-based runs in CI. Previously, pointing a unit's terraform block at a local directory didn't mark the files inside that directory as read, so a change to the module wouldn't select the unit.
When a unit's source is a local module, Terragrunt now records the module's *.tf, *.tf.json, *.hcl, *.tofu, and *.tofu.json files as read by that unit, so --filter 'reading=<path>' and --queue-include-units-reading select the unit when a module file changes:
terragrunt run --all --filter 'reading=./modules/vpc/main.tf' -- planFor files that reading detection doesn't track on its own, the new mark_glob_as_read() HCL function expands a glob and marks every matching file as read in one call:
locals {
configs = mark_glob_as_read("${get_terragrunt_dir()}/config/{*.yaml,**/*.yaml}")
}Existing pipelines built on --queue-include-units-reading or reading= filters may select more units than before, because changes to local module files now count as reads. Previously gated behind the mark-many-as-read experiment, these behaviors no longer require --experiment mark-many-as-read.
--no-discovery-auth-provider-cmdBy default, Terragrunt runs your --auth-provider-cmd once for every unit it discovers, so HCL functions that need credentials resolve correctly during parsing. In a large repository, that can mean hundreds of invocations before any unit runs, which can dominate wall-clock time on change-based runs.
The --no-discovery-auth-provider-cmd flag (env: TG_NO_DISCOVERY_AUTH_PROVIDER_CMD) skips those invocations during the discovery phase, leaving auth to run only for the units that actually execute:
terragrunt run --all \
--no-discovery-auth-provider-cmd \
--queue-include-units-reading=./changed-file.txt \
-- planWarning
Use this only when you know parsing resolves without credentials. Units whose configuration depends on values from --auth-provider-cmd during discovery (for example, via get_aws_account_id()) will fail to parse when the flag is set.
Previously gated behind the opt-out-auth experiment, the flag now works without --experiment opt-out-auth.
Before a run --all, Terragrunt lists the units it's about to run. That list now renders as a dependency tree by default instead of a flat list, with units nested under their dependencies, so the run order and the relationships between units are visible before anything executes:
The following units will be run, starting with dependencies and then their dependents:
.
├── monitoring
╰── vpc
╰── database
╰── backend-app
The header adapts to direction: dependencies come before dependents on apply, and the order reverses on destroy.
Previously gated behind the dag-queue-display experiment, the tree display no longer requires --experiment dag-queue-display.
terragrunt stack generate --filter './my-stack | type=stack' generates only the selected
stack, not the nested stacks it contains, which can be surprising for a stack of stacks.
When a non-glob | type=stack filter leaves a stack's nested stacks ungenerated, Terragrunt
now prints a tip showing how to generate them too, for example
--filter './my-stack | type=stack' --filter './my-stack/** | type=stack'.
permission denied when generated files overwrite CAS-materialized filesWith the CAS enabled, Terragrunt fetches sources as read-only files. Writing a generated file over one of them no longer fails with permission denied:
generate blocks with if_exists = "overwrite", when the module ships the target file (for example, its own versions.tf).terragrunt.values.hcl, when the unit or stack source already contains one.terragrunt.autoinclude.hcl, when the unit or stack source already contains one..terraform.lock.hcl, when the provider cache server updates a committed lock file during init -upgrade.In each case, the read-only file is replaced with a writable one, and the shared CAS store is never modified.
update_source_with_cas on a terraform block when CAS is disabledterragrunt stack generate --no-cas now fails when a generated unit's terraform block sets update_source_with_cas = true, instead of silently emitting the unit with its relative source unchanged. The relative source has no meaning once CAS is disabled, so the generated unit could not resolve its module. This matches the existing behavior for the same attribute on unit and stack blocks, and for a run invoked with --no-cas.
extra_arguments env vars when resolving dependency outputsResolving a dependency block's outputs now applies the env_vars from the unit's terraform extra_arguments blocks whose commands include output.
Git-based filters (for example terragrunt run --all --filter '[HEAD^1...HEAD]' -- plan) now select units
that read an added or deleted file through mark_glob_as_read, even when that file lives outside the unit's
own directory. Previously only modified files outside a unit reached those units; adding or deleting a file
the glob matched left the reading unit out of the run, so its real config change was skipped. Added files are
matched against the newer reference, and deleted files against the older one where the file still exists.
mark_glob_as_read constrains its walk to a boundarymark_glob_as_read now confines glob expansion to a boundary directory. By default the boundary is the enclosing Git repository root; outside a Git repository it is unset. A pattern whose walk would begin outside the boundary returns an error instead of expanding.
This bounds patterns that resolve higher than intended. For example, "${local.dir}/{*.yaml}" becomes /{*.yaml} when local.dir is empty, which previously walked the entire filesystem. A ? : conditional does not prevent this, because HCL evaluates both branches of a conditional before selecting one. Wrapping the call in try lets the error fall back to a default:
locals {
files = sort(try(mark_glob_as_read("${local.dir}/{*.yaml,*.yml,*.json}"), []))
}Pass a leading --terragrunt-boundary argument to set the boundary explicitly, for example to scope the walk to a subdirectory or to widen it to the filesystem root:
locals {
scoped = mark_glob_as_read("--terragrunt-boundary=/etc/terragrunt", "/etc/terragrunt/{*.yaml}")
all = mark_glob_as_read("--terragrunt-boundary=/", "/{*.yaml}")
}terragrunt scaffold now reads input variables from the module directory itself, matching what OpenTofu and Terraform load for a root module. Previously it scanned subdirectories too, so variable blocks defined in nested modules or examples leaked into the scaffolded inputs even though the module never exposes them.
terragrunt stack generate now resolves interpolated object keys in autoinclude blocks (for example
{ "${local.prefix}_key" = ... }), even when the value references dependency.*. Previously the generated
unit kept the key verbatim, leaking a stack-only reference that is not valid in the unit scope.
autoinclude templatesterragrunt stack generate no longer panics when an autoinclude template interpolates a non-string literal (for example "${0}" or "${true}") alongside a dependency.* reference. The interpolated literal is now rendered to its string form (${0} becomes 0) and the dependency reference is preserved for the unit.
autoinclude dependencies on a stack directoryrun --all no longer fails with "does not contain a terragrunt.hcl file" when an autoinclude dependency points at a stack directory (one holding terragrunt.stack.hcl) and the unit is reached transitively through another unit. The dependency cycle check now skips a target with no unit config, matching the direct dependency case.
The following experiments graduated to general availability in this release, and the features they gated are now enabled by default:
stack-dependenciescascatalog-redesignmark-many-as-readopt-out-authdag-queue-displayEach feature is described in the New Features section above.
The corresponding --experiment flags (and TG_EXPERIMENT values) are no longer needed. Passing one still works, but emits a warning about the completed experiment, so you can drop it at your convenience.
Thank you to everyone who ran these experiments early and filed the feedback that got them here.
Starting with this release, Terragrunt releases are published as immutable releases on GitHub. Once a release is published, its tag and assets can no longer be modified or deleted, so the binary you download is guaranteed to be the same binary that was uploaded when the release was published.
See the releases process documentation for details, and Verifying releases with the GitHub CLI for how to check a download against the release attestation.
The install script now checks downloaded release assets against the release attestation that ships with immutable releases. For releases starting with v1.1.0, when an authenticated GitHub CLI (v2.81.0 or later) is available, the script verifies the checksums file and the binary against the attestation before installing, and aborts if either does not match the published release. The check is skipped with a warning when gh is unavailable, too old, or unauthenticated. Use --no-verify-attestation to opt out.
update_source_with_cas integration with --no-cas by @yhakbar in #6363--terragrunt-boundary to mark_glob_as_read by @yhakbar in #6351--parallelism tweaking considerations better by @yhakbar in #6313v1.1.0 changelog polish by @yhakbar in #6333mark-many-as-read experiment by @yhakbar in #6310cas experiment by @yhakbar in #6254dag-queue-display experiment by @yhakbar in #6320opt-out-auth experiment by @yhakbar in #6321go-git by @yhakbar in #6325catalog-redesign experiment by @yhakbar in #6271lll by @yhakbar in #6377TestWindowsTflintIsInvoked by @yhakbar in #6382lll #2 by @yhakbar in #6385go fix ./... by @yhakbar in #6398Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
This is the second release candidate for Terragrunt v1.1.
This is the second release candidate for Terragrunt v1.1.
It carries the same six completed experiments as v1.1.0-rc1, plus bug fixes for those experiments and improvements to how releases are published and verified.
This release completes the following experiments:
stack-dependenciescascatalog-redesignmark-many-as-readopt-out-authdag-queue-displayFuture release candidates for v1.1.0 will include bug fixes related to these experiments or other urgent bug fixes as necessary, and documentation improvements.
Please try out this release candidate in lower environments and share your feedback in the associated GitHub discussion.
mark_glob_as_read constrains its walk to a boundary. Glob expansion is now confined to a boundary directory, defaulting to the enclosing Git repository root. A pattern whose walk would begin outside the boundary returns an error instead of expanding, so a pattern like "${local.dir}/{*.yaml}" that collapses to /{*.yaml} no longer walks the entire filesystem. Pass a leading --terragrunt-boundary argument to set the boundary explicitly. (#6351)
--filter '[HEAD^1...HEAD]' now select units that read an added or deleted file through mark_glob_as_read, even when that file lives outside the unit's own directory. Previously only modified files outside a unit reached those units. (#6352)update_source_with_cas is rejected when CAS is disabled. terragrunt stack generate --no-cas now fails when a generated unit's terraform block sets update_source_with_cas = true, instead of silently emitting an unresolvable relative source. This matches the existing behavior for the same attribute on unit and stack blocks. (#6363)--no-verify-attestation to opt out. (#6344)A stack generates a tree of units from a single terragrunt.stack.hcl file. Wiring one of those units to another used to mean defining dependency blocks in your catalog and threading dependency paths through values. Stack dependencies let you declare those relationships up front instead.
Add an autoinclude block inside a unit or stack block, and Terragrunt generates a partial configuration (a terragrunt.autoinclude.hcl file) next to the generated terragrunt.hcl or terragrunt.stack.hcl that's automatically merged into the unit or stack definition. The new unit.<name>.path and stack.<name>.path references resolve to generated paths, so you don't have to hardcode them:
# terragrunt.stack.hcl
unit "vpc" {
source = "github.com/acme/catalog//units/vpc"
path = "vpc"
}
unit "app" {
source = "github.com/acme/catalog//units/app"
path = "app"
autoinclude {
dependency "vpc" {
config_path = unit.vpc.path
}
inputs = {
vpc_id = dependency.vpc.outputs.vpc_id
}
}
}Anything that's valid in a unit configuration is valid in its autoinclude block, so you can also patch catalog units with configuration they don't ship with, like retry rules:
# terragrunt.stack.hcl
unit "app" {
source = "github.com/acme/catalog//units/app"
path = "app"
autoinclude {
errors {
retry "transient_errors" {
retryable_errors = [".*Error: transient network issue.*"]
max_attempts = 3
sleep_interval_sec = 5
}
}
}
}The same works for nested stacks: an autoinclude block inside a stack block patches the generated terragrunt.stack.hcl, so you can, for example, add an extra unit to one environment without forking the stack in your catalog.
Stack configurations also gained two capabilities along the way:
include blocks now work in terragrunt.stack.hcl files, so shared stack configuration can live in a parent folder.dependency blocks can target stack directories, and the run queue expands them to the units inside. Note that this relationship only goes one way: units can depend on stacks, but stacks cannot depend on stacks or units.See the stacks documentation for the full reference. Previously gated behind the stack-dependencies experiment, all of this is now enabled by default.
The Content Addressable Store (CAS) deduplicates source downloads across configurations. It addresses repositories and modules by their content, stores them locally, and serves later requests from that local store instead of repeating the fetch. This speeds up catalog cloning, OpenTofu/Terraform source fetching, and stack generation, and identical files occupy disk space once regardless of how many configurations use them.
The CAS is no longer limited to Git. It also deduplicates HTTP, Amazon S3, Google Cloud Storage, Mercurial, and SMB sources, along with OpenTofu/Terraform registry sources fetched via tfr://. See supported sources for how each one resolves and deduplicates content.
CAS is enabled by default. Use the --no-cas flag (or TG_NO_CAS=true) to opt out of it for a run:
terragrunt run --all --no-cas -- planTwo new attributes give you finer control, and both default to off:
update_source_with_cas makes a generated stack self-contained. Set it on a unit, stack, or terraform block with a relative source, and terragrunt stack generate rewrites that source into a content-addressed cas:: reference, so the generated tree no longer depends on the surrounding repository layout. Catalog authors can keep relative paths in their sources and still ship a portable, reproducible stack:
# stacks/networking/terragrunt.stack.hcl
unit "vpc" {
source = "../..//units/vpc"
path = "vpc"
update_source_with_cas = true
}After terragrunt stack generate, the relative path is replaced by a reference to the exact tree the CAS stored:
# Generated output
unit "vpc" {
source = "cas::sha1:f39ea0ebf891c9954c89d07b73b487ff938ef08b"
path = "vpc"
update_source_with_cas = true
}mutable controls how the CAS places fetched content on disk. By default, the CAS hard links files from its shared store into .terragrunt-cache and marks them read-only, which is fast and uses no extra space, but means the files can't be edited in place. Set mutable = true on a terraform block to copy the content instead, making the working tree safe to edit at the cost of extra I/O and disk space:
# units/vpc/terragrunt.hcl
terraform {
source = "github.com/acme/catalog//modules/vpc"
mutable = true
}Previously gated behind the cas experiment, the CAS no longer requires --experiment cas.
terragrunt catalogThe catalog command has been redesigned. It now starts without any configuration, discovers components across your catalog repositories in the background, and streams them into the TUI as they're found.
Discovery is no longer limited to a modules/ directory; components can live anywhere in a catalog repository. To control what gets discovered, add a .terragrunt-catalog-ignore file with .gitignore-style globs for the paths you want filtered out.
Components in the TUI now carry metadata to help you navigate a large catalog: each one shows a kind label (template, stack, unit, or module) and optional tags defined in the front-matter of its README.md. From the component list, press s to open a new screen that interactively collects the values used to scaffold the component into your repository.
Previously gated behind the catalog-redesign experiment, the redesigned catalog is now the default terragrunt catalog experience.
Terragrunt can select units by the files they read, which is the basis of change-based runs in CI. Previously, pointing a unit's terraform block at a local directory didn't mark the files inside that directory as read, so a change to the module wouldn't select the unit.
When a unit's source is a local module, Terragrunt now records the module's *.tf, *.tf.json, *.hcl, *.tofu, and *.tofu.json files as read by that unit, so --filter 'reading=<path>' and --queue-include-units-reading select the unit when a module file changes:
terragrunt run --all --filter 'reading=./modules/vpc/main.tf' -- planFor files that reading detection doesn't track on its own, the new mark_glob_as_read() HCL function expands a glob and marks every matching file as read in one call:
locals {
configs = mark_glob_as_read("${get_terragrunt_dir()}/config/{*.yaml,**/*.yaml}")
}Existing pipelines built on --queue-include-units-reading or reading= filters may select more units than before, because changes to local module files now count as reads. Previously gated behind the mark-many-as-read experiment, these behaviors no longer require --experiment mark-many-as-read.
--no-discovery-auth-provider-cmdBy default, Terragrunt runs your --auth-provider-cmd once for every unit it discovers, so HCL functions that need credentials resolve correctly during parsing. In a large repository, that can mean hundreds of invocations before any unit runs, which can dominate wall-clock time on change-based runs.
The --no-discovery-auth-provider-cmd flag (env: TG_NO_DISCOVERY_AUTH_PROVIDER_CMD) skips those invocations during the discovery phase, leaving auth to run only for the units that actually execute:
terragrunt run --all \
--no-discovery-auth-provider-cmd \
--queue-include-units-reading=./changed-file.txt \
-- planWarning
Use this only when you know parsing resolves without credentials. Units whose configuration depends on values from --auth-provider-cmd during discovery (for example, via get_aws_account_id()) will fail to parse when the flag is set.
Previously gated behind the opt-out-auth experiment, the flag now works without --experiment opt-out-auth.
Before a run --all, Terragrunt lists the units it's about to run. That list now renders as a dependency tree by default instead of a flat list, with units nested under their dependencies, so the run order and the relationships between units are visible before anything executes:
The following units will be run, starting with dependencies and then their dependents:
.
├── monitoring
╰── vpc
╰── database
╰── backend-app
The header adapts to direction: dependencies come before dependents on apply, and the order reverses on destroy.
Previously gated behind the dag-queue-display experiment, the tree display no longer requires --experiment dag-queue-display.
permission denied when generated files overwrite CAS-materialized filesWith the CAS enabled, Terragrunt fetches sources as read-only files. Writing a generated file over one of them no longer fails with permission denied:
generate blocks with if_exists = "overwrite", when the module ships the target file (for example, its own versions.tf).terragrunt.values.hcl, when the unit or stack source already contains one.terragrunt.autoinclude.hcl, when the unit or stack source already contains one..terraform.lock.hcl, when the provider cache server updates a committed lock file during init -upgrade.In each case, the read-only file is replaced with a writable one, and the shared CAS store is never modified.
update_source_with_cas on a terraform block when CAS is disabledterragrunt stack generate --no-cas now fails when a generated unit's terraform block sets update_source_with_cas = true, instead of silently emitting the unit with its relative source unchanged. The relative source has no meaning once CAS is disabled, so the generated unit could not resolve its module. This matches the existing behavior for the same attribute on unit and stack blocks, and for a run invoked with --no-cas.
Git-based filters (for example terragrunt run --all --filter '[HEAD^1...HEAD]' -- plan) now select units
that read an added or deleted file through mark_glob_as_read, even when that file lives outside the unit's
own directory. Previously only modified files outside a unit reached those units; adding or deleting a file
the glob matched left the reading unit out of the run, so its real config change was skipped. Added files are
matched against the newer reference, and deleted files against the older one where the file still exists.
mark_glob_as_read constrains its walk to a boundarymark_glob_as_read now confines glob expansion to a boundary directory. By default the boundary is the enclosing Git repository root; outside a Git repository it is unset. A pattern whose walk would begin outside the boundary returns an error instead of expanding.
This bounds patterns that resolve higher than intended. For example, "${local.dir}/{*.yaml}" becomes /{*.yaml} when local.dir is empty, which previously walked the entire filesystem. A ? : conditional does not prevent this, because HCL evaluates both branches of a conditional before selecting one. Wrapping the call in try lets the error fall back to a default:
locals {
files = sort(try(mark_glob_as_read("${local.dir}/{*.yaml,*.yml,*.json}"), []))
}Pass a leading --terragrunt-boundary argument to set the boundary explicitly, for example to scope the walk to a subdirectory or to widen it to the filesystem root:
locals {
scoped = mark_glob_as_read("--terragrunt-boundary=/etc/terragrunt", "/etc/terragrunt/{*.yaml}")
all = mark_glob_as_read("--terragrunt-boundary=/", "/{*.yaml}")
}terragrunt stack generate now resolves interpolated object keys in autoinclude blocks (for example
{ "${local.prefix}_key" = ... }), even when the value references dependency.*. Previously the generated
unit kept the key verbatim, leaking a stack-only reference that is not valid in the unit scope.
autoinclude templatesterragrunt stack generate no longer panics when an autoinclude template interpolates a non-string literal (for example "${0}" or "${true}") alongside a dependency.* reference. The interpolated literal is now rendered to its string form (${0} becomes 0) and the dependency reference is preserved for the unit.
The following experiments graduated to general availability in this release, and the features they gated are now enabled by default:
stack-dependenciescascatalog-redesignmark-many-as-readopt-out-authdag-queue-displayEach feature is described in the New Features section above.
The corresponding --experiment flags (and TG_EXPERIMENT values) are no longer needed. Passing one still works, but emits a warning about the completed experiment, so you can drop it at your convenience.
Thank you to everyone who ran these experiments early and filed the feedback that got them here.
Starting with this release, Terragrunt releases are published as immutable releases on GitHub. Once a release is published, its tag and assets can no longer be modified or deleted, so the binary you download is guaranteed to be the same binary that was uploaded when the release was published.
See the releases process documentation for details, and Verifying releases with the GitHub CLI for how to check a download against the release attestation.
The install script now checks downloaded release assets against the release attestation that ships with immutable releases. For releases starting with v1.1.0, when an authenticated GitHub CLI (v2.81.0 or later) is available, the script verifies the checksums file and the binary against the attestation before installing, and aborts if either does not match the published release. The check is skipped with a warning when gh is unavailable, too old, or unauthenticated. Use --no-verify-attestation to opt out.
update_source_with_cas integration with --no-cas by @yhakbar in #6363--terragrunt-boundary to mark_glob_as_read by @yhakbar in #6351--parallelism tweaking considerations better by @yhakbar in #6313v1.1.0 changelog polish by @yhakbar in #6333mark-many-as-read experiment by @yhakbar in #6310cas experiment by @yhakbar in #6254dag-queue-display experiment by @yhakbar in #6320opt-out-auth experiment by @yhakbar in #6321go-git by @yhakbar in #6325catalog-redesign experiment by @yhakbar in #6271Nothing published for this version
Nothing published for this version
This is the first release candidate for Terragrunt v1.1.
This is the first release candidate for Terragrunt v1.1.
This release completes the following experiments:
stack-dependenciescascatalog-redesignmark-many-as-readopt-out-authdag-queue-displayFuture release candidates for v1.1.0 will include bug fixes related to these experiments or other urgent bug fixes as necessary, and documentation improvements.
Please try out this release candidate in lower environments and share your feedback in the Associated GitHub discussion.
A stack generates a tree of units from a single terragrunt.stack.hcl file. Wiring one of those units to another used to mean defining dependency blocks in your catalog and threading dependency paths through values. Stack dependencies let you declare those relationships up front instead.
Add an autoinclude block inside a unit or stack block, and Terragrunt generates a partial configuration (a terragrunt.autoinclude.hcl file) next to the generated terragrunt.hcl or terragrunt.stack.hcl that's automatically merged into the unit or stack definition. The new unit.<name>.path and stack.<name>.path references resolve to generated paths, so you don't have to hardcode them:
# terragrunt.stack.hcl
unit "vpc" {
source = "github.com/acme/catalog//units/vpc"
path = "vpc"
}
unit "app" {
source = "github.com/acme/catalog//units/app"
path = "app"
autoinclude {
dependency "vpc" {
config_path = unit.vpc.path
}
inputs = {
vpc_id = dependency.vpc.outputs.vpc_id
}
}
}Anything that's valid in a unit configuration is valid in its autoinclude block, so you can also patch catalog units with configuration they don't ship with, like retry rules:
# terragrunt.stack.hcl
unit "app" {
source = "github.com/acme/catalog//units/app"
path = "app"
autoinclude {
errors {
retry "transient_errors" {
retryable_errors = [".*Error: transient network issue.*"]
max_attempts = 3
sleep_interval_sec = 5
}
}
}
}The same works for nested stacks: an autoinclude block inside a stack block patches the generated terragrunt.stack.hcl, so you can, for example, add an extra unit to one environment without forking the stack in your catalog.
Stack configurations also gained two capabilities along the way:
include blocks now work in terragrunt.stack.hcl files, so shared stack configuration can live in a parent folder.dependency blocks can target stack directories, and the run queue expands them to the units inside. Note that this relationship only goes one way: units can depend on stacks, but stacks cannot depend on stacks or units.See the stacks documentation for the full reference. Previously gated behind the stack-dependencies experiment, all of this is now enabled by default.
The Content Addressable Store (CAS) deduplicates source downloads across configurations. It addresses repositories and modules by their content, stores them locally, and serves later requests from that local store instead of repeating the fetch. This speeds up catalog cloning, OpenTofu/Terraform source fetching, and stack generation, and identical files occupy disk space once regardless of how many configurations use them.
The CAS is no longer limited to Git. It also deduplicates HTTP, Amazon S3, Google Cloud Storage, Mercurial, and SMB sources, along with OpenTofu/Terraform registry sources fetched via tfr://. See supported sources for how each one resolves and deduplicates content.
CAS is enabled by default. Use the --no-cas flag (or TG_NO_CAS=true) to opt out of it for a run:
terragrunt run --all --no-cas -- planTwo new attributes give you finer control, and both default to off:
update_source_with_cas makes a generated stack self-contained. Set it on a unit, stack, or terraform block with a relative source, and terragrunt stack generate rewrites that source into a content-addressed cas:: reference, so the generated tree no longer depends on the surrounding repository layout. Catalog authors can keep relative paths in their sources and still ship a portable, reproducible stack:
# stacks/networking/terragrunt.stack.hcl
unit "vpc" {
source = "../..//units/vpc"
path = "vpc"
update_source_with_cas = true
}After terragrunt stack generate, the relative path is replaced by a reference to the exact tree the CAS stored:
# Generated output
unit "vpc" {
source = "cas::sha1:f39ea0ebf891c9954c89d07b73b487ff938ef08b"
path = "vpc"
update_source_with_cas = true
}mutable controls how the CAS places fetched content on disk. By default, the CAS hard links files from its shared store into .terragrunt-cache and marks them read-only, which is fast and uses no extra space, but means the files can't be edited in place. Set mutable = true on a terraform block to copy the content instead, making the working tree safe to edit at the cost of extra I/O and disk space:
# units/vpc/terragrunt.hcl
terraform {
source = "github.com/acme/catalog//modules/vpc"
mutable = true
}Previously gated behind the cas experiment, the CAS no longer requires --experiment cas.
terragrunt catalogThe catalog command has been redesigned. It now starts without any configuration, discovers components across your catalog repositories in the background, and streams them into the TUI as they're found.
Discovery is no longer limited to a modules/ directory; components can live anywhere in a catalog repository. To control what gets discovered, add a .terragrunt-catalog-ignore file with .gitignore-style globs for the paths you want filtered out.
Components in the TUI now carry metadata to help you navigate a large catalog: each one shows a kind label (template, stack, unit, or module) and optional tags defined in the front-matter of its README.md. From the component list, press s to open a new screen that interactively collects the values used to scaffold the component into your repository.
Previously gated behind the catalog-redesign experiment, the redesigned catalog is now the default terragrunt catalog experience.
Terragrunt can select units by the files they read, which is the basis of change-based runs in CI. Previously, pointing a unit's terraform block at a local directory didn't mark the files inside that directory as read, so a change to the module wouldn't select the unit.
When a unit's source is a local module, Terragrunt now records the module's *.tf, *.tf.json, *.hcl, *.tofu, and *.tofu.json files as read by that unit, so --filter 'reading=<path>' and --queue-include-units-reading select the unit when a module file changes:
terragrunt run --all --filter 'reading=./modules/vpc/main.tf' -- planFor files that reading detection doesn't track on its own, the new mark_glob_as_read() HCL function expands a glob and marks every matching file as read in one call:
locals {
configs = mark_glob_as_read("${get_terragrunt_dir()}/config/{*.yaml,**/*.yaml}")
}Existing pipelines built on --queue-include-units-reading or reading= filters may select more units than before, because changes to local module files now count as reads. Previously gated behind the mark-many-as-read experiment, these behaviors no longer require --experiment mark-many-as-read.
--no-discovery-auth-provider-cmdBy default, Terragrunt runs your --auth-provider-cmd once for every unit it discovers, so HCL functions that need credentials resolve correctly during parsing. In a large repository, that can mean hundreds of invocations before any unit runs, which can dominate wall-clock time on change-based runs.
The --no-discovery-auth-provider-cmd flag (env: TG_NO_DISCOVERY_AUTH_PROVIDER_CMD) skips those invocations during the discovery phase, leaving auth to run only for the units that actually execute:
terragrunt run --all \
--no-discovery-auth-provider-cmd \
--queue-include-units-reading=./changed-file.txt \
-- planWarning
Use this only when you know parsing resolves without credentials. Units whose configuration depends on values from --auth-provider-cmd during discovery (for example, via get_aws_account_id()) will fail to parse when the flag is set.
Previously gated behind the opt-out-auth experiment, the flag now works without --experiment opt-out-auth.
Before a run --all, Terragrunt lists the units it's about to run. That list now renders as a dependency tree by default instead of a flat list, with units nested under their dependencies, so the run order and the relationships between units are visible before anything executes:
The following units will be run, starting with dependencies and then their dependents:
.
├── monitoring
╰── vpc
╰── database
╰── backend-app
The header adapts to direction: dependencies come before dependents on apply, and the order reverses on destroy.
Previously gated behind the dag-queue-display experiment, the tree display no longer requires --experiment dag-queue-display.
permission denied when generated files overwrite CAS-materialized filesWith the CAS enabled, Terragrunt fetches sources as read-only files. Writing a generated file over one of them no longer fails with permission denied:
generate blocks with if_exists = "overwrite", when the module ships the target file (for example, its own versions.tf).terragrunt.values.hcl, when the unit or stack source already contains one.terragrunt.autoinclude.hcl, when the unit or stack source already contains one..terraform.lock.hcl, when the provider cache server updates a committed lock file during init -upgrade.In each case, the read-only file is replaced with a writable one, and the shared CAS store is never modified.
terragrunt stack generate now resolves interpolated object keys in autoinclude blocks (for example
{ "${local.prefix}_key" = ... }), even when the value references dependency.*. Previously the generated
unit kept the key verbatim, leaking a stack-only reference that is not valid in the unit scope.
autoinclude templatesterragrunt stack generate no longer panics when an autoinclude template interpolates a non-string literal (for example "${0}" or "${true}") alongside a dependency.* reference. The interpolated literal is now rendered to its string form (${0} becomes 0) and the dependency reference is preserved for the unit.
The following experiments graduated to general availability in this release, and the features they gated are now enabled by default:
stack-dependenciescascatalog-redesignmark-many-as-readopt-out-authdag-queue-displayEach feature is described in the New Features section above.
The corresponding --experiment flags (and TG_EXPERIMENT values) are no longer needed. Passing one still works, but emits a warning about the completed experiment, so you can drop it at your convenience.
Thank you to everyone who ran these experiments early and filed the feedback that got them here.
--parallelism tweaking considerations better by @yhakbar in #6313v1.1.0 changelog polish by @yhakbar in #6333mark-many-as-read experiment by @yhakbar in #6310cas experiment by @yhakbar in #6254dag-queue-display experiment by @yhakbar in #6320opt-out-auth experiment by @yhakbar in #6321go-git by @yhakbar in #6325Your coding agent can read these notes before it upgrades. Set up the MCP server →