Terragrunt (1.x)
Single skill for all Terragrunt work, organized as a router: identify the task mode below,
read ONLY the listed reference(s), then act. References are grep-friendly — prefer
grep lookups over reading whole files.
Hard policy
Post-1.0 CLI only. Never generate or recommend pre-1.0 forms:
run-all,plan-all,hclfmt,hclvalidate,graph-dependencies,validate-inputs,terragrunt-prefixed flags, theskipattribute,retryable_errors, or barefind_in_parent_folders()pointing at a rootterragrunt.hcl. If user code contains these, flag them and propose the 1.x form. Emit the replacement, never just the ban:run --all,hcl fmt,hcl validate,dag graph,hcl validate --inputs(add--strictto fail on inputs the module does not declare), the unprefixed flag, theexcludeblock,errors { retry { retryable_errors = [...] } }(the attribute name is unchanged; what moved is its position, so a bare top-levelretryable_errorsis the pre-1.0 form and the nested one is current), andfind_in_parent_folders("root.hcl").hcl validate --inputsis spelled out here because an ablation showed it is the one banned form this policy was carrying on its own. With the ban removed, every other form held at zero violations — the surrounding 1.x examples were enough — whilevalidate-inputs, removed in v1.0.0, accounted for 16 of 16 remaining violations. It was the only one with no correct form shown anywhere in this file. Seeevals/RESULTS.md.Fact-based generation. Every generated pattern must trace to a documented Gruntwork pattern (references here carry doc links to docs.terragrunt.com). Don't invent layouts.
Knowledge freshness. This skill does not assert what the current Terragrunt release is. That claim has a half-life of about six weeks and it expired unnoticed: this line said "current stable v1.1.2" while the binary on the author's own machine was already v1.1.3. Run
scripts/preflight.pyinstead — it readsterragrunt --versionand reports which of the gates below your build satisfies, which it does not reach, and which upgrade hazards are in effect on it. Everything that follows is a fact about when a feature landed, which does not rot. v1.1.0 graduated six experiments to GA —stack-dependencies,cas,catalog-redesign,mark-many-as-read,opt-out-auth,dag-queue-display— so their features are now enabled by default; passing the old--experiment/TG_EXPERIMENTvalue only prints a "completed experiment" warning. The stack-dependency features (autoinclude,unit.<name>.path/stack.<name>.path,dependencyon stack dirs viaautoinclude) and the CAS attributes (update_source_with_cas,mutable) therefore require v1.1.0+ — flag them and do NOT emit them for repos pinned to ≤1.0.x.v1.1.1 added two experiments (opt-in, not GA), both on the
terraformblock and both requiring v1.1.1+:oci(module sources from OCI registries viaoci://) andversion-attribute(aversionconstraint fortfr://registry modules). Syntax and the gating rules are inreferences/hcl-blocks.mdunder## BLOCK: terraform. v1.1.1 was otherwise a bug-fix release — it introduced no new GA surface.v1.1.2 added no new GA surface either, but two of its fixes change what to advise. Recommend v1.1.2+ rather than v1.1.1 wherever either applies:
- The provider cache server's archive-download endpoint did not require the run's
token before v1.1.2, so another local process could use a running cache server to pull
from a private registry with the starting user's registry credentials. Relevant on
shared CI runners. See
references/scale-and-performance.md. - v1.1.1 specifically broke
iam_role(and--iam-assume-role/TG_IAM_ASSUME_ROLE) when combined with static AWS credentials: backend operations assumed the role a second time, so it tried to assume itself and AWS returnedAccessDenied. The error points at the trust policy, but editing the trust policy is the wrong fix — upgrading is. Seereferences/hcl-blocks.mdunder## BLOCK: iam_role.
v1.1.3 graduated nothing. No experiment stabilised, so the "enabled by default" list above is unchanged from v1.1.0. It is twelve bug fixes and five new experiments, but two items in it change what to advise and one changes behaviour whether or not you opt in.
Recommend v1.1.3+ wherever
--provider-cacheis used, or wherever two Terragrunt runs can overlap on one machine. Two separate race conditions were fixed, and both are the same shape as the v1.1.2 provider-cache issue above:- The cache server could start answering requests before it had finished preparing its directories. A provider requested in that window had its archive and lock file written relative to the working directory, leaving zip files loose in the project.
- Two concurrent runs staged provider downloads at the same path, so the first to finish
could delete an archive the other was still unpacking, failing it with
failed to open zip archive. Relevant to any parallel CI matrix on a shared runner. Seereferences/scale-and-performance.md.
THE ONE THAT BITES WITHOUT OPTING IN:
(and)are now reserved in--filter. Thebounded-discoveryexperiment introduces an inline(dir)boundary operand, and reserving those characters changes how--filterparses everywhere, not only when the experiment is enabled.--filter '1...(foo | bar)'previously matched a unit literally named(fooorbar); on v1.1.3 it is rejected as a malformed boundary. Wrap a name or path containing parentheses in braces —--filter '{./weird(name)}'— to keep it literal. This is the v1.1.1-iam_role-shaped item for this release: an upgrade can break a working invocation with nothing enabled. Check filter strings and unit names for parentheses before upgrading.v1.1.3 added five experiments, all opt-in, none GA:
block-iteration(reserves theexpansionblock for iteratingdependency/unit/stackovercount/for_each, plus anenabledattribute onunitandstack— reserved only in this release, enabling it has no behavioural effect, but writingexpansionwithout it is now an error rather than a silent discard),bounded-discovery(above),browse-tui(terragrunt browse, a three-column TUI estate browser),mutable-generate(see the collision warning below) andoptional-dependency-outputs(--no-dependency-outputs, the global form ofskip_outputs). Syntax and gating inreferences/hcl-blocks.md.NAME COLLISION —
mutablenow means two different things. On theunitblock it is GA since v1.1.0 and needs no experiment. On thegenerateblock it is new in v1.1.3 and requires--experiment mutable-generate; setting it without the experiment is an error, and earlier versions reject the attribute outright. Do not carry the v1.1.0 gate across togenerate. Both are inreferences/hcl-blocks.md.Experiments are not a short list, and they move in patch releases. Alongside the two above,
azure-backend,deep-merge,dependency-fetch-output-from-state,hook-context-env,iac-engine,optional-hooks,slow-task-reportingandsymlinkswere active as of v1.1.1, and v1.1.2 addedotel-logs(OpenTelemetry logs signal viaTG_TELEMETRY_LOGS_EXPORTER) andprofiling(pprof CPU/heap/goroutine collection for debugging Terragrunt itself, not the infrastructure it manages) — twelve active as of v1.1.2 — and that count was already short by one:catalog-formatwas active and is not in the list above. The authoritative list gives eighteen active as of v1.1.3, so do not reach the number by adding this release's five to a previous total; read the page. v1.1.2 also changed two existing ones:azure-backendwent from inert to functional (seereferences/azure-backend.md— this reverses a long-standing "Terragrunt never bootstraps Azure state" rule), andocigained CAS caching plus Docker credential-helper auth. These references cover only some of them, so an unfamiliarv1.1.4 graduated nothing either, and its most important content is two bugs it FIXES that have been live since v1.1.0. Both entered when CAS became the default path for git sources, and both went four releases unnoticed because neither reads like a Terragrunt problem:
- A
sourceURL carrying the go-getterdepthquery parameter has not downloaded since v1.1.0....vpc.git?depth=1&ref=v5.21.0— CAS liftsrefout of the URL but leftdepthin place, sogitreceives...vpc.git?depth=1and rejects it as an invalid repository name. Adepthwith noreffails identically. This presents as "my module source is wrong", not "my Terragrunt is old", which is exactly why it is worth knowing. The clone depth comes from--cas-clone-depth(default 1) and adepthon the URL is never applied to a CAS clone, so the parameter can simply be deleted — that fixes it on every version, without upgrading. - With CAS on, reading a local source that has already been initialised failed and fell
back to the slower standard copy, logging
CAS processing failed ... source escapes repository rootwhen generating a stack. Provider caching was the cause: both the Provider Cache Server and the Automatic Provider Cache Dir leave.terraformpointing into a shared cache outside the source, and CAS read that as the source escaping itself. v1.1.4 leaves.terraformand.terragrunt-cacheout of local sources while keeping.terraform.lock.hcl, sotofu initin a source directory no longer changes its CAS key.
THE ONE THAT BITES WITHOUT OPTING IN, this release: binary selection when
--tf-pathis unset. Terragrunt used to choose betweentofuandterraformby runningtofu -version, a process launch on every command includingfindandlistthat never run the binary. That launch is gone —terragrunt --versionbenchmarks ~1.7× faster — but the selection changed with it. Terragrunt now pickstofuwhenevertofuis onPATH, and reports the failure if it cannot run, where it previously fell back toterraformsilently. A machine with a broken or half-installedtofustops working on upgrade. Set--tf-pathorTG_TF_PATHexplicitly if you were relying on that fallback.terragrunt scaffoldis now interactive from a terminal. It opens the same form the Catalog TUI uses instead of writing# TODOplaceholders — listing a source's variables for a module or template, and thevalues.*references a unit or stack makes, which are written toterragrunt.values.hcl. Dismissing it withescwrites nothing at all. The old placeholder behaviour still applies under--non-interactive, whenstdinis not a terminal, or when the source asks for nothing — so CI is unchanged, but a scripted runbook or an agent driving a PTY will meet a form where a file used to appear. Pass--non-interactivein anything automated.v1.1.4 added one strict control:
duplicate-dependency-labels. Twodependencyblocks sharing a label parsed without error and silently resolved every reference to whichever came last. That now warns, and is an error under--strict-control duplicate-dependency-labels. Seereferences/hcl-blocks.mdunder## BLOCK: dependency.--experimentvalue is not evidence that it is wrong — look it up rather than flagging it. For anything newer, niche, or not found in the references, use the C7 search skill (Context7) or fetch docs.terragrunt.com directly — do not guess.- The provider cache server's archive-download endpoint did not require the run's
token before v1.1.2, so another local process could use a running cache server to pull
from a private registry with the starting user's registry credentials. Relevant on
shared CI runners. See
Terragrunt orchestrates OpenTofu or Terraform; don't assume one unless the user's repo indicates it (
.terraform-version,terraform_binary, provider constraints, or anengineblock — the latter is gated behind theiac-engineexperiment and is not covered inreferences/hcl-blocks.md, so look it up before editing one).
Terminology (1.0)
Unit = directory with terragrunt.hcl deploying one module. Stack = group of units;
implicit (directory tree) or explicit (terragrunt.stack.hcl). Catalog = library of
reusable unit/module definitions. Targeting uses --filter expressions.
Mode router
| Task | Mode | Read first |
|---|---|---|
| "Create/scaffold/set up" configs, envs, stacks | GENERATE | references/architecture-patterns.md + relevant templates/ |
| "Validate/lint/check/CI" existing configs | VALIDATE | validate.sh header (abs path in VALIDATE workflow); references/cli-reference.md as needed |
| "Review/audit/best practice" a repo or file | REVIEW | references/best-practices.md |
| Error message pasted / "why is this failing" | DIAGNOSE | grep references/error-patterns.md |
| "What does X do" (block/function/command) | LOOKUP | grep the matching reference below |
| Complex/edge-case examples (multi-account, CI, mocks) | EXAMPLES | references/advanced-examples.md |
| Anything Azure backend/provider (state, auth, gotchas) | (any mode) | also references/azure-backend.md |
"Only run changed units", slow run --all, CI fan-out, performance at scale |
SCALE | references/scale-and-performance.md |
| CI/CD pipeline, OIDC auth to AWS/GCP/Azure, saving plans between plan and apply | CICD | references/cicd.md |
Look up a module, a resource type, or their inputs/outputs — before pinning a source or writing inputs |
(hand off) | the terraform-registry skill, not this one |
"Migrate to stacks", convert an _envcommon/tree layout to terragrunt.stack.hcl |
MIGRATE | references/architecture-patterns.md ## PATTERN: migrate an existing tree to explicit stacks |
Reference index (grep, don't read whole files)
Quick navigation. Every reference is written to be grepped by a heading convention, so the fastest route to an answer is the grep handle, not the filename.
| Reference | Holds | Entries | Grep handle |
|---|---|---|---|
error-patterns.md |
diagnosed errors: likely causes for every one, a fix for 51 | 63 | ^## ERROR: |
functions.md |
built-in functions by category | 31 | ^## FUNCTION: |
best-practices.md |
practices, plus comparisons and decision guides | 29 / 7 / 3 | ^## PRACTICE: ^## COMPARISON: ^## DECISION: |
cli-reference.md |
the 1.x command tree and the --filter system |
24 | ^## COMMAND: |
hcl-blocks.md |
every HCL block and attribute | 15 / 10 | ^## BLOCK: ^## ATTRIBUTE: |
advanced-examples.md |
worked examples: multi-account/subscription/project, CI, mocks, AVM, CFT, own-module | 28 | ^## EXAMPLE: |
architecture-patterns.md |
layout patterns, catalog/live repo shape, migration to stacks | 7 | ^## PATTERN: |
cicd.md |
OIDC per cloud, plan-then-apply across a stack | — | grep a ^## heading |
azure-backend.md |
Azure state, auth and provider gotchas | — | read whole; it is short |
scale-and-performance.md |
run only what changed, cache, parallelism | — | read whole; it is short |
Two of the ten carry no heading convention because they are short enough to read end to end.
Counts are verified against the files, not asserted: regenerate with
grep -c '^## ERROR:' references/error-patterns.md and so on.
references/architecture-patterns.md— layout patterns, env-agnostic root rule, unit/stack model, dependency wiring, runtime control. Headings:## PATTERN:references/hcl-blocks.md— all HCL blocks (terraform, remote_state, dependency, include, generate, locals, inputs, feature, exclude, errors...).grep '^## BLOCK: dependency'references/functions.md— built-in functions by category.grep '^## FUNCTION: get_env'references/cli-reference.md— full 1.0 command tree +--filtersystem.grep '^## COMMAND: stack run'references/error-patterns.md— 63 diagnosed errors. Every one names likely causes; 51 carry a fix, and an entry with no**Solutions:**section has none to give — say so rather than improvising one. Grep error keywords first:grep -in 'state lock' references/error-patterns.mdreferences/best-practices.md— practices with priority/rationale/antipatterns, plus## COMPARISON:(e.g. dependency vs dependencies) and## DECISION:guidesreferences/advanced-examples.md— 28 worked examples.grep '^## EXAMPLE:'references/azure-backend.md— Azure (azurerm) remote state + provider setup and gotchas: whether Terragrunt bootstraps Azure depends on version + experiment (no by default, yes on v1.1.2+ with--experiment azure-backend), backend key list, auth methods,use_azuread_auth/Entra ID, provider v4subscription_id, RBAC + shared-key gotchas, OIDC for CI. Read this for ANY Azure backend/provider task.references/cicd.md— CI/CD: OIDC to AWS (incl. the immutablesubclaim that breaks pipelines on repos created from 2026-07-15), GCP Workload Identity Federation, a pointer to the Azure section, and--out-dirfor saving a plan per unit between plan and apply. Read for anything about pipelines or CI authentication.references/scale-and-performance.md— running only changed units/stacks at scale:--filtergit+graph targeting (--filter-affected),find --jsonCI matrices, provider cache server, CAS, dependency-output-from-state, parallelism, per-unit overhead, OSS vs paid Scale. Read for "only plan/apply what changed", slowrun --all, or CI fan-out.
Templates
templates/root/root.hcl— root config (environment-agnostic)templates/child/terragrunt.hcl— unit including root + env.hcltemplates/env/env.hcl— per-environment localstemplates/stack/terragrunt.stack.hcl,templates/catalog/— explicit stacks & catalog unitstemplates/module/terragrunt.hcl— standalone unittemplates/backends/— remote_state for S3/GCS/Azure, essential + advanced tiers. Azure caveat: by defaultazurermpasses through to the native backend and Terragrunt does NOT bootstrap/migrate/delete Azure storage, so the account/container must pre-exist — which is what these templates assume. On v1.1.2+ with--experiment azure-backendthat reverses and Terragrunt does manage them. Establish the version before advising; full detail inreferences/azure-backend.md.templates/providers/— providergenerateblocks for all three clouds (aws-generate-provider.hcl,azure-generate-provider.hcl,gcp-generate-provider.hcl). Each cloud constrains the target differently, and one of them does not constrain it at all: AWS hasallowed_account_ids;azurermv4+ makessubscription_idrequired (seereferences/azure-backend.md); thegoogleprovider has no equivalent and no required argument whatsoever, so an unsetprojectfalls through toGOOGLE_PROJECT, then ADC, then whatevergcloud config set projectlast selected. Deriveprojectfrom the directory tree rather than typing it.
Replace ALL placeholder variables before presenting ({{mustache}} in templates/backends and
templates/providers; [BRACKET] style everywhere else); never leave placeholders or invent
secrets/account IDs — ask or use obvious dummies labelled as such.
GENERATE workflow
- Determine pattern via references/architecture-patterns.md, then print the pattern
selection checklist from that file before writing anything. Not a summary of it, not
"the pattern is X" in prose — the filled-in block. Three recorded runs of this workflow
skipped it and two of those chose a layout the request had explicitly ruled out; the
checklist is where the account-vs-region decision is forced into the open. If accounts
and regions both vary, read
## PATTERN: two accounts, many regionsfirst. - Read the relevant template(s); adapt, don't freestyle.
- Verify the include/read graph: every
find_in_parent_folders/read_terragrunt_configtarget must exist from the referencing file's location. - Validate if tooling exists (see VALIDATE); otherwise state what wasn't validated.
- Present: directory tree, file list, run commands (
terragrunt run --all plan), and any placeholders the user must fill.
VALIDATE workflow
Bundled scripts run by absolute path. They live in this skill's base directory (announced when the skill loads, usually
~/.claude/skills/terragrunt-skill). You'll be working inside an IaC repo, so a relativescripts/…won't resolve — always use the base-dir path. The Python helper is stdlib-only: preferuv run python <path>, falling back topython3 <path>if uv isn't on PATH (UV="$(command -v uv || ls "$HOME/.local/bin/uv" /opt/homebrew/bin/uv /usr/local/bin/uv 2>/dev/null | head -1)").
bash ~/.claude/skills/terragrunt-skill/scripts/validate.sh [DIR] runs the layered suite:
hcl fmt --check, hcl validate, tflint, Trivy, dag check, optional plan. Control via env
vars: SKIP_PLAN, SKIP_SECURITY, SKIP_LINT, SKIP_INIT, SKIP_BACKEND_INIT=true
(CI/offline: init with -backend=false), SOFT_FAIL_SECURITY. No terragrunt binary available?
Fall back to static review: check 1.0-only policy violations, include-graph integrity, then
REVIEW mode checklist. uv run python ~/.claude/skills/terragrunt-skill/scripts/detect_custom_resources.py [DIR]
finds non-registry providers/modules needing research.
DIAGNOSE workflow
- Extract distinctive tokens from the error (e.g. "state lock", "Could not find").
grep -in '<token>' references/error-patterns.md; read matched## ERROR:sections.- No match → C7 search / docs.terragrunt.com troubleshooting; say the pattern wasn't in the embedded set.
REVIEW workflow
Audit against best-practices.md as a checklist; report findings ordered by priority with the practice name, why it matters, and the doc link. Include 1.0-policy violations (Hard policy item 1) as findings.
Provenance
This skill is MIT licensed. It is not wholly original, and the parts that are not are named here.
Harvested content. Five reference files — advanced-examples.md, best-practices.md,
error-patterns.md, functions.md and hcl-blocks.md — began as curated data from
omattsson/terragrunt-mcp-server
(MIT), restructured here for grep-based lookup and since re-checked against
docs.terragrunt.com. Each file repeats this in its own header. Together they are roughly
two-thirds of the reference corpus by size, so it is the single largest input to this skill
after the Terragrunt documentation itself.
Two things follow, and both matter. MIT permits the reuse and requires the notice, which
is why this paragraph exists. And that repository's last commit predates Terragrunt v1.0.0
by five weeks, so anything harvested from it is pre-1.0 material by default — the
re-checks are what make it safe, not the source. Where a re-check has not happened, treat
the entry as suspect rather than current. references/hcl-blocks.md is known to still carry
three pre-1.0 retry blocks for exactly this reason.
Layout and scaffolding guidance describes, but does not copy, Gruntwork's published
reference repositories:
terragrunt-infrastructure-catalog-example
(MPL-2.0),
terragrunt-infrastructure-live-example
(Apache-2.0), and gruntwork-io/boilerplate
(MPL-2.0), the templating engine behind scaffold and catalog.
Terragrunt is © Gruntwork, Inc. (MIT). This skill is not affiliated with or endorsed by
Gruntwork. The bundled scripts/validate.sh invokes external tools when present —
terragrunt, tflint (MPL-2.0) and trivy (Apache-2.0) — but does not bundle them; their
own licenses apply.