Terragrunt 1.0 made the CLI a stable interface. Every flag, command, and piece of serialized output is covered by a backwards compatibility guarantee that holds for the whole 1.x series: what works today keeps working. That kind of promise only makes sense on a CLI design worth keeping for years, so in the releases leading up to 1.0, we gave it a comprehensive redesign.
If you've been using Terragrunt for years, you may still be typing run-all and --terragrunt-non-interactive out of muscle memory, and your CI scripts may still export TERRAGRUNT_ prefixed environment variables. This post is a tour of the CLI as it exists today: what changed, why it changed, and what the modern invocations look like.
Why the CLI was redesigned
The old CLI grew organically around one design decision: Terragrunt wrapped OpenTofu/Terraform, and any command it didn't recognize got forwarded to the wrapped binary, and unfortunately, that decision had consequences.
Every Terragrunt flag needed a terragrunt- prefix to avoid colliding with OpenTofu/Terraform flags, which made every flag eleven characters longer than it needed to be. Every flag had to be globally available, because Terragrunt couldn't know in advance which command it was wrapping, so terragrunt --help printed a wall of options regardless of what you were doing. Misspell a command and the error came from tofu, telling you OpenTofu had no command by that name, which was technically true and completely unhelpful. On any run, Terragrunt would attempt to provision state buckets and lock tables if they didn't exist, whether or not you asked it to, and throw errors at you if it failed.
RFC #3445 laid all of this out and proposed a CLI that follows a predictable set of rules instead. The timing was deliberate. We wanted to get the surface closer to one we could sustainably manage, then commit to it with the 1.0 guarantee.
run
The core of the redesign is the run command. When you want Terragrunt to orchestrate an OpenTofu/Terraform command, you say so:
Shortcuts still exist for the commands you use constantly, so terragrunt plan and terragrunt apply work exactly as they always did. What changed is the default for everything else: unknown commands are no longer forwarded to OpenTofu/Terraform. If you want to run a command that doesn't have a shortcut, you ask for it explicitly:
The -- separates Terragrunt's flags from the arguments destined for the wrapped binary. The same pattern works for passing OpenTofu/Terraform flags through:
run also absorbed two commands you may know well. run-all is now a flag:
And the old graph command, which never graphed anything (it ran commands across the dependency graph), is now run --graph. One verb, with flags that modify its scope, instead of three commands with overlapping behavior.
exec
run always invokes OpenTofu/Terraform. exec invokes whatever you want, with the same configuration context a run would have: resolved dependencies, fetched source, and inputs exposed as TF_VAR_ environment variables.
Say a unit defines this:
You can lean on that configuration from any command:
This turns Terragrunt into a general-purpose context loader for operational tasks. Anything that needs to know what a unit knows (its inputs, its resolved source, its dependency outputs) can get that context without reimplementing HCL parsing. The --in-download-dir flag runs the command inside the unit's fetched module code, which is handy for inspecting what Terragrunt actually downloaded:
backend bootstrap
Before the redesign, Terragrunt provisioned backend resources implicitly. Run terragrunt plan against a unit with a remote_state block, and if the S3 bucket didn't exist, Terragrunt created it. Convenient the first time, surprising every time after, especially for teams who wanted tight control over what gets created in their accounts.
Provisioning is now its own command:
It reads the remote_state block and ensures the resources it describes exist. For an S3 backend, that means the state bucket (with encryption, versioning, and TLS enforcement), the DynamoDB lock table (if configured), and the access logging bucket if you configured one. The command is idempotent: resources that already exist and match the configuration are left alone, and resources that drifted are updated when it's safe to do so.
If you preferred the old automatic behavior, it's still available as an explicit opt-in, either with the --backend-bootstrap flag on a run or the TG_BACKEND_BOOTSTRAP environment variable in your CI environment.
bootstrap has siblings under the same command: backend migrate moves state between backends, and backend delete removes backend state files. Both of these have safety checks to ensure that state is versioned, etc. to help mitigate the risk of state manipulation. Backend lifecycle management used to be a mix of implicit behavior and manual cloud console work; now it's a command group.
find and list
The redesign also added two commands for answering a question that gets harder to answer as a repository grows: what's actually in here?
list gives you the human-readable view:
The tree format shows the hierarchy:
And the long format with --dependencies shows how units relate:
The list command is designed to be the most convenient way for humans to explore their infrastructure estates, with multiple different displays supported for visualization of the estate.
find is the programmatic counterpart. It emits structured output meant for scripts and tooling:
The --dag flag sorts output in dependency order, and --queue-construct-as shows the order Terragrunt would walk the graph for a given command. The difference matters most for destroy, which walks the graph in reverse: dependents come down before the units they depend on.
If you were using output-module-groups to drive CI logic, find --dag --json --dependencies is its replacement, and a better one: it hands you the actual dependency edges rather than precomputed groups, so your tooling can decide for itself when each unit is free to run.
Consistent flags and environment variables
Underneath the new commands sits a quieter change that probably affects more of your scripts than anything above: naming. The terragrunt- prefix is gone from every flag, and every environment variable moved from TERRAGRUNT_ to TG_ (with backwards compatibility for old environment variable names).
Most renames are mechanical, but a few flags were updated further for clarity along the way. --terragrunt-debug became --inputs-debug, because that's what it actually does (it writes a debug .tfvars file for root-causing input issues). The migration guide has the full table, including the handful of flags that were renamed more substantially or removed.
A few commands were reorganized at the same time, mostly into command groups that collect related behavior:
hclfmtis nowhcl fmt, andhclvalidateishcl validateterragrunt-infois nowinfo printgraph-dependenciesis nowdag graphrender-jsonis nowrender --json -w
The pattern is the same one backend follows: a noun for the domain, a verb for the action.
The 1.x stability guarantee
All of this matters because of what happened at 1.0. CLI flags, serialized output from commands like find, HCL configuration, and the way Terragrunt enriches OpenTofu/Terraform stdout and stderr are all stable for the entire 1.x series. Scripts you write against this CLI today will keep working across 1.x upgrades.
That guarantee is the reason the redesign happened when it did. You only get to make that promise once, and making it on the old CLI would have locked in a decade of quirks. The cleanup came first so that the stable surface would be one worth keeping.
It also makes the CLI a foundation other tooling can build on.
Wrapping up
The streamlined CLI comes down to a few ideas: one orchestration verb (run) with flags instead of command variants, arbitrary command execution with unit context (exec), explicit backend management (backend bootstrap), real discovery tooling (find and list), and naming that follows rules you can guess. The CLI reference documents the full surface, and the migration guide covers every rename if you're moving older scripts forward.
A good way to start: run terragrunt list --tree at the root of a repository you work in and see what it tells you. Or pick one CI script still using run-all and TERRAGRUNT_ variables and migrate it; the rename table makes it a short job.
Improvements like the CLI redesign come out of the work we do supporting large Terragrunt estates. If that's the problem you're solving, check out Terragrunt Scale.



- No-nonsense DevOps insights
- Expert guidance
- Latest trends on IaC, automation, and DevOps
- Real-world best practices



