Skip to content

Running Many Units at Once

terragrunt run --all plan and terragrunt run --all apply operate on every unit under the current directory tree at once, in the dependency order established by their dependency blocks.

Terminal window
terragrunt run --all plan
terragrunt run --all apply

run --all discovers every unit under the current working directory, builds the dependency graph from their dependency blocks — covered in the previous lesson — and runs the given command against each one in the correct order. vpc applies before ec2 automatically; nobody has to remember or hard-code that sequencing.

A word about older syntax you will see in older material

Section titled “A word about older syntax you will see in older material”

Terragrunt’s multi-unit command surface has changed, and a lot of existing blog posts and answers online still describe the old one. Two generations of history are worth knowing so stale advice does not trip you up:

terragrunt plan-all and terragrunt apply-all (along with destroy-all, output-all, and validate-all) have been completely removed from current Terragrunt. They do not exist anymore and will simply fail — do not use them, and treat any tutorial that leans on them as written for a much older version:

Terminal window
# Removed entirely — these commands no longer exist in current Terragrunt
terragrunt plan-all
terragrunt apply-all

terragrunt run-all COMMAND went through the same fate one release later: deprecated first, then removed entirely once the CLI redesign shipped in current Terragrunt. Running it today fails immediately with unknown command: "run-all", the same as plan-all:

Terminal window
# Removed in current Terragrunt — fails with "unknown command: run-all"; use run --all instead
terragrunt run-all plan

Scoping a run —all, and running non-interactively

Section titled “Scoping a run —all, and running non-interactively”

Sometimes you do not want every unit in the tree, only a subset — say, everything under one region while leaving the rest of the tree untouched. The --filter flag does that, matching units by a path glob. Flags placed before the command need a -- separator so Terragrunt knows where its own flags end and the per-unit command begins:

Terminal window
# Only plan units under ./networking (and anything depending on them)
terragrunt run --all --filter './networking/**' -- plan

For CI, prompting for a manual confirmation on every unit is not an option — --non-interactive skips those prompts so an apply can run unattended:

Terminal window
terragrunt run --all --non-interactive -- apply

A terraform block inside a unit’s terragrunt.hcl can define before_hook and after_hook entries — arbitrary shell commands Terragrunt runs immediately before or after a given operation on that specific unit:

ec2/terragrunt.hcl
terraform {
before_hook "validate_before_plan" {
commands = ["plan"]
execute = ["./scripts/validate-tags.sh"]
}
after_hook "notify_slack" {
commands = ["apply"]
execute = ["./scripts/notify-slack.sh"]
run_on_error = false
}
}

commands lists which Terragrunt commands trigger the hook, execute is the actual command Terragrunt runs — as an argument list, not a shell string — and run_on_error controls whether an after_hook still fires if the operation it is attached to failed. During a run --all apply across many units, each unit’s own hooks fire around that unit’s own apply — the Slack notification above fires once per unit that successfully applies, not once for the whole run.

flowchart LR
  cmd["terragrunt run --all apply"] --> vpc["vpc unit applies first"]
  vpc --> ec2["ec2 unit applies (depends on vpc)"]
  vpc --> rds["rds unit applies (depends on vpc)"]
  vpc -->|after_hook| slack["notify-slack.sh"]
run --all apply fanning out across units in dependency order, with a hook firing after one unit's apply

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, ec2 and rds both depending on vpc, local backend, same shape as the previous lesson). terragrunt find --dag and terragrunt list --tree --dag — both discovery commands, no plan/apply involved — read the same dependency blocks a run --all would, real output:

$ terragrunt find --dag
vpc
ec2
rds
$ terragrunt list --tree --dag
.
╰── vpc
├── ec2
╰── rds

And the ordering log from an actual run --all plan, identical shape, confirming find/list --dag is not a separate approximation but the real execution plan:

$ terragrunt run --all --non-interactive -- plan
INFO The following units will be run, starting with dependencies and then their dependents:
╰── vpc
├── ec2
╰── rds

--filter scoping to just rds, real output — note only one unit runs, vpc is not pulled in for a plan because mock_outputs_allowed_terraform_commands = ["plan"] lets rds succeed without needing vpc’s real state at all:

$ terragrunt run --all --filter './rds' --non-interactive -- plan
INFO The following units will be run, starting with dependencies and then their dependents:
╰── rds
❯❯ Run Summary 1 units
Succeeded 1

--queue-include-dir: soft-deprecated, not removed — do not confuse it with plan-all

Section titled “--queue-include-dir: soft-deprecated, not removed — do not confuse it with plan-all”

This is worth being precise about, because getting it wrong reads as a contradiction of the removal story above. --queue-include-dir/--queue-exclude-dir are the older scoping flags --filter replaced — but “replaced” does not mean “removed.” Real proof, running the old flag directly against this course’s installed Terragrunt 1.1.0:

$ terragrunt run --all --queue-include-dir './rds' -- plan
INFO The following units will be run, starting with dependencies and then their dependents:
╰── rds
INFO [rds] terraform: Initializing the backend...
INFO [rds] terraform: Terraform has been successfully initialized!

No unknown flag error, no rejection — the flag is accepted and the run proceeds exactly like --filter './rds' above. Compare that directly against the genuinely-removed surface from earlier in this lesson:

$ terragrunt run-all plan
ERROR unknown command: "run-all". ...
$ terragrunt run --all --terragrunt-non-interactive -- plan
ERROR flag `-terragrunt-non-interactive` is not a Terragrunt flag. ...

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 — this course’s own installed 1.1.0 confirms that distinction with a real run, not just the docs’ wording. run-all/plan-all/apply-all and the old --terragrunt-* flag prefix are hard-removed and error immediately; --queue-include-dir is soft-deprecated and still works today. Prefer --filter for new work, but do not assume every old flag you meet in an existing codebase is broken — check with terragrunt run --help before rewriting it.

errors { retry / ignore }: the current block name

Section titled “errors { retry / ignore }: the current block name”

The spec-sounding names error_hook/on_errors are not what current Terragrunt actually calls this — the real block is errors, with retry and ignore sub-blocks, each keyed by a label and a list of regexes matched against the error text:

terragrunt.hcl
errors {
ignore "missing_file" {
ignorable_errors = [".*no file exists at.*"]
message = "Ignoring missing source file for this demo unit"
}
retry "transient_network" {
retryable_errors = [".*Error: transient network issue.*"]
max_attempts = 3
sleep_interval_sec = 5
}
}

Proof the ignore block genuinely changes the exit code — a unit whose plan fails for real (file() referencing a file that does not exist, a real Invalid function argument error from Terraform), with an ignore block whose regex matches that exact error text:

$ terragrunt run --non-interactive -- plan
STDERR terraform: Error: Invalid function argument
STDERR terraform: Invalid value for "path" parameter: no file exists at
"./does-not-exist.txt"; ...
WARN Ignoring error, reason: Ignoring missing source file for this demo unit
$ echo $?
0

Without the ignore block, this exact same failing plan exits non-zero and run --all stops at this unit (or continues past it only with --queue-ignore-errors, a blunter, whole-run tool). With it, the specific, matched error is downgraded to a warning and the run reports success — worth being careful with, since a regex just slightly wrong (matching more than intended) silently swallows failures a CI pipeline was supposed to catch.

flowchart TD
  fail["A unit command fails"] --> check{"errors block present?"}
  check -->|no| bubble["Failure bubbles up to run --all as-is"]
  check -->|ignore matches| warn["Logged as WARN, unit reported succeeded"]
  check -->|retry matches| again["Retried up to max_attempts, sleep_interval_sec apart"]
  check -->|no match| bubble
Ignorable and retryable error handling inside errors, evaluated before a failure reaches run --all's own error handling

When reviewing generated multi-unit run configs, check for:

  • plan-all/apply-all/run-all anywhere in a script or CI pipeline — proven above, all three fail immediately with unknown command on current Terragrunt; this is the single most common stale-tutorial artifact in generated Terragrunt code.
  • terragrunt run --all --queue-include-dir ... -- plan treated as broken or rewritten with a comment like “removed in new Terragrunt” — proven above, this flag still runs correctly; only --filter is the current recommendation, not the only working one.
  • An ignorable_errors/retryable_errors regex that looks plausible but doesn’t match the actual provider error text — proven above with a first attempt (.*no such file or directory.*) that silently failed to match Terraform’s real wording (no file exists at), meaning the ignore block did nothing and the failure still propagated. Always test the regex against a real captured error, never a guessed one.
  • --filter used with a glob that doesn’t include a leading ./ or trailing /** consistently — Terragrunt’s filter syntax is a real path-glob dialect, not shell globbing; copy the exact form from terragrunt run --all --filter --help rather than guessing shell-glob syntax.

🐛 Spot the bug

# CI pipeline snippet
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 elsewhere in the stack trace. A real provider bug that happens to mention “DNS resolution failed” as one line inside a much larger, unrelated error (a misconfigured resource whose error message also references a DNS name) gets silently ignored too, because regex matching has no concept of “this is the root cause vs. incidental text.” Proven above: the mechanism downgrades any matching error to a WARN and reports the unit as succeeded — a broad regex like this is exactly the failure mode described in the “Reading AI-generated code” list, just inverted (too broad instead of not matching at all).

run --all is Terragrunt doing, at the unit level, exactly what terraform apply does at the resource level inside one state: build a DAG (proven above via find --dag/list --dag returning the identical order a real run --all uses), then walk it. --filter restricts which nodes of that graph get visited at all — proven above, filtering to rds alone did not pull in vpc, because mock_outputs meant rds’s plan needed no real vpc data. 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.

ChoiceBenefitCost
run --all over N sequential manual runsCorrect dependency order computed automatically, proven aboveA mistake in one unit’s dependency graph (or an over-broad --filter) affects every unit downstream in that same run
--filter over --queue-include-dirCurrent, documented syntax; a real path-glob dialect with union/AND semantics --queue-* never hadExisting scripts using --queue-include-dir are not broken (proven above) — rewriting them is a choice, not a requirement
errors { ignore }A known, understood failure mode (proven above: a genuinely broken file() call) does not halt an entire run --allProven above with the SpotTheBug: a regex broader than intended silently swallows unrelated real failures, with no distinction between “the exact known issue” and “any error containing similar text”
before_hook/after_hook per unitPer-unit side effects (tag validation, Slack notification) without a separate orchestration scriptHooks fire once per unit in a run --all, not once for the whole run — a notification hook fires N times across N units, which needs to be designed for
  • “plan-all, apply-all, and run-all are all just older spellings of run —all that still work.” Proven above with real errors: plan-all/apply-all/run-all all fail immediately with unknown command. Only run --all (with the space, not the hyphen) works.
  • “Any flag that isn’t in the current docs must be broken.” Proven above: --queue-include-dir is absent from the headline --filter documentation but still runs a real init+plan with 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, including unrelated failures that mention similar words.
  • “errors { retry } and errors { ignore } are the same thing with different names.” They are genuinely different actions on a match: retry re-runs the operation up to max_attempts times, ignore logs a warning and treats the unit as succeeded without re-running anything — picking the wrong one for a transient-vs-permanent failure produces very different (and sometimes very wrong) behavior.
What is the current command for running plan across every unit in a directory tree?
What happened to terragrunt plan-all and apply-all?
What is the status of the terragrunt run-all command?
What is a before_hook or after_hook used for?
On this course's installed Terragrunt 1.1.0, what happens when you run terragrunt run --all --queue-include-dir './rds' -- plan, the flag --filter is documented to have replaced?
An errors { ignore "x" { ignorable_errors = [".*no such file or directory.*"] } } block is added to catch a real Terraform error whose actual text is 'no file exists at "./does-not-exist.txt"'. What happens when that error occurs?