Lesson 5 · This repo's IaC: Terragrunt & the env×org matrix
Terragrunt & DRY
The wrapper that keeps ~180 configs from copy-pasting the same backend block 180 times.
Your win: explain what Terragrunt adds over plain Terraform, and read this
repo's root.hcl + a leaf config — the DRY machinery behind the whole
terraform/ tree.
The repetition plain Terraform leaves you with
Modules (Lesson 4) DRY up the resources. But every place you run Terraform still needs its own backend config (which state bucket + key) and provider config — and in a repo with ~180 places to run it, you'd copy that same boilerplate 180 times, differing only by the state key.1 Terragrunt is a thin wrapper that fixes exactly this: define the backend + provider once, and every leaf inherits it.
The root config — write it once
root.hcl, not the conventional terragrunt.hcl.) It
holds the shared config for every leaf:
remote_state { backend = "gcs" … prefix = path_relative_to_include() }(:11-23) — the state backend (Lesson 3), with a per-leaf prefix so each component gets isolated state.generate "provider"(:25-34) — writes aprovider.tf(thegoogleprovider) into each leaf.generate "versions"(:39-55) — Terraform>= 1.9,google+google-beta.inputs = merge(env, common)(:57) — feeds shared variables down.
A leaf — thin, and it just includes the root
include { path = find_in_parent_folders("root.hcl") }(:1-2) — pull in the root (backend + provider + versions).terraform { source = "../../..//modules/platforms" }(:5-6) — point at the module to instantiate (Lesson 4).dependency "vpc" { }(:9) — wire another component's outputs in; e.g.private_network = dependency.vpc.outputs.network_self_link(:116).include "env" { path = ".../_env/platforms.hcl", expose = true }(:13-15) — pull in shared component logic (Lesson 6).
include for the shared config, source for
the module, dependency for cross-component outputs. Every one of the ~180 leaves
is built from this.
includes it and points at a module." Plain Terraform makes you repeat
the backend block everywhere; Terragrunt's generate + include
generate it for you. It's the same "write once, stamp out many" instinct as modules — applied
one level up, to the wiring instead of the resources.
Terragrunt — Keep remote state DRY + config blocks
The remote_state/generate/include blocks and the DRY
rationale.
→ terragrunt — Keep remote state DRY
→ terragrunt — Config blocks & attributes
Check yourself (from memory)
Q1. Terragrunt's main job over plain Terraform is to…
includes it — no 180× copy-paste.
Q2. In this repo, a leaf points at the module to instantiate via…
terraform.source names the module;
include brings the root config, dependency the cross-component outputs.
Q3. The repo's Terragrunt root config file is named…
root.hcl (not the conventional
terragrunt.hcl); leaves find_in_parent_folders("root.hcl").
live/root.hcl (NOT terragrunt.hcl):
remote_state gcs backend + per-leaf prefix path_relative_to_include()
(:11-23), generate "provider" (:25), generate "versions" (TF≥1.9, :39),
inputs = merge(env, common) (:57). A LEAF (…/platforms/terragrunt.hcl):
include { find_in_parent_folders("root.hcl") } + terraform { source =
"…/modules/platforms" } + dependency "vpc" {} (outputs→inputs) + include
"env" (shared logic, expose=true). Grammar: include=config, source=module,
dependency=wiring.path_relative_to_include() gives each leaf a unique state key, or
what run-all does across leaves? Ask me.