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 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:

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

This 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:

Terminal window
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' -- plan
INFO The following units will be run, starting with dependencies and then their dependents:
╰── database

No 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:

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

before_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:

compute/terragrunt.hcl
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"]
run --all apply fanning out in dependency order, with a hook after one unit

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 --dag
vpc
compute
database
$ terragrunt list --tree --dag
.
╰── vpc
├── compute
╰── database

And the ordering log from an actual run --all plan, identical shape:

$ terragrunt run --all --non-interactive -- plan
INFO 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 -- plan
INFO The following units will be run, starting with dependencies and then their dependents:
╰── database
❯❯ Run Summary 1 units
Succeeded 1

errors { 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:

terragrunt.hcl
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 -- plan
STDERR terraform: Error: Invalid function argument
WARN Ignoring error, reason: Ignoring missing source file for this demo unit
$ echo $?
0

Without 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.

  • plan-all/apply-all/run-all anywhere in a script or CI pipeline — proven above, all three fail immediately with unknown command.
  • 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.
  • An ignorable_errors regex 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.”

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.

ChoiceBenefitCost
--filter over --queue-include-dirCurrent, documented syntax, a real path-glob dialectExisting scripts using --queue-include-dir are not broken (proven above)
errors { ignore }A known, understood failure mode does not halt an entire run --allProven above: a regex broader than intended silently swallows unrelated real failures
  • “Any flag that isn’t in the current docs must be broken.” Proven above: --queue-include-dir 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.
What is the current command for running plan across every unit under the current directory tree?
What is true about plan-all, apply-all, and run-all today?
What does a before_hook or after_hook block let you do?
What determines the order units are processed in during a run --all apply?
On this course installed Terragrunt 1.1.0, what happens when you run terragrunt run --all --queue-include-dir './database' -- 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'. What happens?