Running Many Units at Once
The idea in one sentence
Section titled “The idea in one sentence”terragrunt run --all plan and terragrunt run --all apply operate on every unit under the current directory tree at once, in the dependency order Terragrunt already computes from each unit’s dependency blocks, and can be scoped down with --filter or wrapped with before_hook/after_hook for anything that needs to happen around a specific unit’s run.
run —all: operating on every unit at once
Section titled “run —all: operating on every unit at once”Applying a vpc unit, then a compute unit, then a database unit one at a time by hand works, but it does not scale past a handful of units, and it puts the burden of remembering the right order back on you. run --all removes that by pointing Terragrunt at a directory and telling it to run the given command against every unit underneath it:
terragrunt run --all planterragrunt run --all applyThis is the current command syntax. It is worth being explicit about what it replaced, because a lot of existing material online still teaches the old forms: terragrunt run-all plan — without the space before --all — and the older terragrunt plan-all and terragrunt apply-all commands have all been removed outright on a current Terragrunt install. Running any of them does not just print a deprecation warning and continue; it fails immediately with unknown command, since Terragrunt no longer forwards unrecognized commands by default. If a tutorial, blog post, or internal wiki still shows run-all, plan-all, or apply-all, treat that as a sign the content predates the current CLI and verify the rest of it before trusting it.
Scoping with —filter, and running non-interactively in CI
Section titled “Scoping with —filter, and running non-interactively in CI”Running run --all from the very top of a repository touches every unit in it, which is rarely what you want for a quick iteration on one part of the tree. The --filter flag scopes a run --all down to a subset of units by path:
terragrunt run --all --filter './project-a/**' -- plan--filter is the current, unified way to scope a multi-unit run; it replaced the older --queue-include-dir and --queue-exclude-dir flags. Be precise about what “replaced” means here, because it sits right next to a genuinely-removed command family in this same lesson: the old flags are soft-deprecated, not removed. Real proof, running the old flag directly against this course’s installed Terragrunt 1.1.0:
$ terragrunt run --all --queue-include-dir './database' -- planINFO The following units will be run, starting with dependencies and then their dependents:╰── databaseNo unknown flag error — the flag is accepted and the run proceeds exactly like --filter './database' would. Compare that against run-all/plan-all/apply-all above, which fail immediately with a real unknown command error — those are hard-removed. Per the Terragrunt CLI-redesign migration guide, --queue-include-dir/--queue-exclude-dir/--queue-strict-include are described as flags to migrate away from, not flags that were deleted. The double dash before plan separates Terragrunt’s own flags from the command being run against each matched unit.
CI pipelines need one more flag: Terragrunt normally prompts for confirmation before some operations, which has no meaning in a non-interactive pipeline run. --non-interactive skips those prompts, replacing the older --terragrunt-non-interactive flag:
terragrunt run --all --non-interactive -- applybefore_hook and after_hook: running commands around a unit operation
Section titled “before_hook and after_hook: running commands around a unit operation”A dependency block and run --all handle ordering resources across units, but plenty of real workflows need to run an arbitrary shell command tied to a specific unit’s operation — validating something before a plan, or notifying a team after an apply succeeds. before_hook and after_hook blocks, declared inside a unit’s terraform block, do exactly that:
terraform { before_hook "validate_naming" { commands = ["plan", "apply"] execute = ["./scripts/validate-naming.sh"] }
after_hook "notify_slack" { commands = ["apply"] execute = ["./scripts/notify-slack.sh", "compute unit applied"] run_on_error = false }}commands lists which Terragrunt commands trigger the hook — validate_naming above runs before either plan or apply on this unit, while notify_slack only runs after a successful apply. execute is the actual command Terragrunt runs, given as a list rather than a single shell string. run_on_error = false on notify_slack means the Slack notification only fires when the apply actually succeeds; setting it to true would fire the hook even after a failed run, which is the right choice for a hook that needs to clean something up or alert on failure regardless of outcome.
flowchart TD cmd["terragrunt run --all apply"] cmd --> vpc["vpc unit"] vpc --> compute["compute unit (depends on vpc)"] vpc --> database["database unit (depends on vpc)"] compute --> hook["after_hook: notify_slack"]
Proof: real ordering, find/list, and --filter on a three-unit tree
Section titled “Proof: real ordering, find/list, and --filter on a three-unit tree”A real three-unit tree (vpc, and compute/database both depending on vpc, local backend). terragrunt find --dag and terragrunt list --tree --dag — discovery commands, no plan/apply — read the same dependency blocks a run --all would:
$ terragrunt find --dagvpccomputedatabase
$ terragrunt list --tree --dag.╰── vpc ├── compute ╰── databaseAnd the ordering log from an actual run --all plan, identical shape:
$ terragrunt run --all --non-interactive -- planINFO The following units will be run, starting with dependencies and then their dependents:╰── vpc ├── compute ╰── database--filter scoping to just database — only that one unit runs, vpc is not pulled in since mock_outputs_allowed_terraform_commands = ["plan"] lets database succeed without needing vpc’s real state:
$ terragrunt run --all --filter './database' --non-interactive -- planINFO The following units will be run, starting with dependencies and then their dependents:╰── database
❯❯ Run Summary 1 units Succeeded 1errors { retry / ignore }: the current block name
Section titled “errors { retry / ignore }: the current block name”Current Terragrunt calls this block errors, with retry and ignore sub-blocks:
errors { ignore "missing_file" { ignorable_errors = [".*no file exists at.*"] message = "Ignoring missing source file for this demo unit" }}Proof the ignore block genuinely changes the exit code — a unit whose plan fails for real (file() referencing a file that does not exist), with a matching ignore block:
$ terragrunt run --non-interactive -- planSTDERR terraform: Error: Invalid function argumentWARN Ignoring error, reason: Ignoring missing source file for this demo unit$ echo $?0Without the ignore block, this exact failing plan exits non-zero. With it, the matched error is downgraded to a warning and the run reports success — a regex broader than intended silently swallows failures a CI pipeline was supposed to catch.
Reading AI-generated code
Section titled “Reading AI-generated code”plan-all/apply-all/run-allanywhere in a script or CI pipeline — proven above, all three fail immediately withunknown command.terragrunt run --all --queue-include-dir ... -- plantreated as broken or rewritten with a comment like “removed in new Terragrunt” — proven above, this flag still runs correctly.- An
ignorable_errorsregex that looks plausible but doesn’t match the actual provider error text — test the regex against a real captured error, never a guessed one.
🐛 Spot the bug
errors {ignore "flaky_dns" { ignorable_errors = [".*DNS resolution failed.*"] message = "Known flaky DNS in this CI runner, ignoring"}}Show the answer
Not a syntax bug — a scope bug. ignorable_errors regexes match against any error text produced during that unit’s run, not specifically DNS-resolution failures. A real provider bug that happens to mention “DNS resolution failed” as one line inside a much larger, unrelated error gets silently ignored too, because regex matching has no concept of “this is the root cause vs. incidental text.”
Mental Model
Section titled “Mental Model”run --all builds a DAG (proven above via find --dag/list --dag returning the identical order a real run --all uses), then walks it. --filter restricts which nodes get visited at all. The errors block sits one layer below that walk, deciding per-unit whether a failure is fatal to the whole run or gets logged and treated as a (dangerous, if the regex is too broad) success.
Trade-offs
Section titled “Trade-offs”| Choice | Benefit | Cost |
|---|---|---|
--filter over --queue-include-dir | Current, documented syntax, a real path-glob dialect | Existing scripts using --queue-include-dir are not broken (proven above) |
errors { ignore } | A known, understood failure mode does not halt an entire run --all | Proven above: a regex broader than intended silently swallows unrelated real failures |
Common Misconceptions
Section titled “Common Misconceptions”- “Any flag that isn’t in the current docs must be broken.” Proven above:
--queue-include-dirstill runs a realinit+planwith zero errors on this course’s installed 1.1.0 — soft-deprecated is not the same as removed. - “An
errors { ignore }block only matches the exact error scenario I wrote it for.” Proven above with the SpotTheBug: it matches any error text the regex happens to hit.