Terragrunt Explained — When You Actually Need It
Terragrunt keeps large Terraform setups DRY and orchestrated, but small teams often pay its learning curve for little return.
Key takeaways
Terragrunt keeps large Terraform setups DRY and orchestrated, but small teams often pay its learning curve for little return.
On this page
Terragrunt shows up in a lot of infrastructure repos, and it shows up in a lot of arguments too. Some teams swear it saved them from copy-paste hell. Others rip it out after a year and wonder why they added a whole extra tool. Both camps are usually right, because Terragrunt earns its keep in specific conditions and becomes dead weight outside them.
This post walks through what Terragrunt is, the concrete problems it solves, what it costs you, and how to decide whether your setup is one where it pays off.
What Terragrunt actually is#
Terragrunt is a thin wrapper around Terraform (and now OpenTofu). It does not replace Terraform. Under the hood it still calls the same binary, generates the same plan, and manages the same state. What it adds is a layer that handles the boilerplate Terraform makes you repeat: backend configuration, provider blocks, variable wiring, and running commands across many modules at once.
If you have never hit that boilerplate as a real problem, that sentence probably sounds like a solution looking for a problem. That is the honest tension at the center of Terragrunt. It only makes sense once your repo is big enough that repetition genuinely hurts.
The problems it solves#
DRY backend config: In plain Terraform, every module that keeps remote state needs a backend block, and those blocks cannot use variables. So you end up hardcoding the same S3 bucket, region, and a slightly different key in dozens of places. Terragrunt generates that block for you from one shared definition, changing only the state key per module.
DRY provider config: The same story applies to provider blocks, required_providers, assume-role settings, and default tags. Terragrunt can generate a provider.tf into each module so you define the pattern once and inherit it everywhere.
Many environments and state files: When you run dev, staging, and prod as separate state files (which you should for blast-radius reasons), the input differences between them are usually small. Terragrunt lets you keep the module code in one place and feed each environment its own inputs, without duplicating the module per environment.
Dependencies between modules: Real infrastructure has ordering. The VPC has to exist before the database, the cluster before the app. Terragrunt models this with dependency blocks that pass one module's outputs into another's inputs and enforce apply order.
run-all across a stack: Once dependencies are declared, terragrunt run-all apply walks the whole graph and applies everything in the right order. For a platform team managing dozens of components, that single command is a big part of the appeal.
The terragrunt.hcl model#
Each leaf directory holds a small terragrunt.hcl that points at a module and supplies inputs. Shared settings live in a parent file that children inherit through include.
# live/prod/us-east-1/database/terragrunt.hcl
include "root" {
path = find_in_parent_folders()
}
terraform {
source = "git::git@github.com:acme/modules.git//rds?ref=v1.4.0"
}
dependency "vpc" {
config_path = "../vpc"
}
inputs = {
instance_class = "db.r6g.large"
subnet_ids = dependency.vpc.outputs.private_subnet_ids
environment = "prod"
}
The root file (found via find_in_parent_folders) holds the backend and provider generation that every module shares, so no leaf ever repeats it.
The folder layout is where the model clicks. You typically split immutable module code from the live configuration that instantiates it:
infra/
├── modules/ # reusable Terraform modules
│ ├── vpc/
│ ├── rds/
│ └── eks/
└── live/
├── terragrunt.hcl # root: backend + provider generation
├── dev/
│ └── us-east-1/
│ ├── vpc/terragrunt.hcl
│ ├── database/terragrunt.hcl
│ └── eks/terragrunt.hcl
└── prod/
└── us-east-1/
├── vpc/terragrunt.hcl
├── database/terragrunt.hcl
└── eks/terragrunt.hcl
Adding a new environment becomes a matter of copying a directory of small config files, not duplicating module logic.
The downsides#
Terragrunt is not free. The costs are real and worth naming.
Bold: It is another layer. You now debug across two tools. When something breaks, you have to work out whether the fault is in your Terraform, in the generated code, or in Terragrunt's orchestration. Generated backend.tf and provider.tf files are not in your source, which surprises people reading the repo for the first time.
Learning curve: HCL functions like find_in_parent_folders, read_terragrunt_config, and the dependency/mock_outputs dance take time to learn. New engineers who already know Terraform still have to learn a second mental model on top.
Overlap with native features: Terraform has closed some of the gap Terragrunt originally filled. Partial backend config via -backend-config files, provider default tags, and the newer stacks and test tooling all reduce the "you have no other option" argument. Terragrunt still does more, but the case is narrower than it was in 2019.
Alternatives#
Native workspaces: Good for a handful of near-identical environments off one config. They share a backend key prefix and get awkward once environments diverge, so treat them as a light-touch option rather than a scaling strategy.
Terramate: A newer orchestration layer that generates Terraform code and adds change detection, so it only runs stacks that actually changed. Worth a look if orchestration is your main pain rather than DRY config.
Plain modules plus a wrapper script: A well-factored module library plus a short Makefile or shell script covers a surprising number of teams. You lose run-all and dependency graphs, but you keep the stack simple.
For where these sit against each other, see our roundup of the best Infrastructure-as-Code tools. If you are also weighing engines, Terraform vs OpenTofu is the companion read.
Deciding#
Ask a few blunt questions:
- How many state files do you manage? Under ten, the DRY payoff is small.
- How many environments, and how different are they? Many near-identical environments is Terragrunt's sweet spot.
- Do you have real cross-module dependencies you apply together? If yes,
run-allis genuinely useful. - Who maintains this? A platform team can absorb the tool. A single app team often cannot justify it.
If most answers point at "small and simple," native Terraform will serve you better and read more plainly to the next person.
The call we'd make#
Reach for Terragrunt when you are running many environments across many components with a platform team that owns the tooling. That is exactly the situation it was built for, and it will save you from a repo full of near-duplicate backend blocks and manual apply ordering.
Skip it when you have one or two environments, a modest number of state files, and no dedicated platform owner. In that world the extra layer costs more attention than it returns, and plain Terraform modules with a thin script will keep your setup easier to reason about. Start without it, and adopt it the day repetition and apply-ordering stop being annoyances and start being incidents.
Get the DevOps Troubleshooting Cheat Sheet
Subscribe and get our free one-page reference for the errors that eat an afternoon — CrashLoopBackOff, OOMKilled, Terraform state locks, and more — plus new guides as we publish them.
LLM API Pricing Compared — Cost per Million Tokens in 2026
A practitioner's guide to how LLM API pricing works, how to estimate a workload's monthly bill, and the levers that actually cut it.
Best Infrastructure-as-Code Tools in 2026 — Terraform, OpenTofu, Pulumi, and More
The IaC landscape fractured after the Terraform license change. This is the map to what each tool is actually best at, and how to choose without regret.
More from DevOps
Explore more articles in this category
Best Kubernetes IDE and GUI Tools in 2026
kubectl is fine until you're juggling five namespaces across three clusters. These are the tools that make that manageable, compared.
Chef vs Puppet vs Ansible: Configuration Management in 2026
One is agentless and Python-based, the other two run a persistent agent and a domain-specific language. The architecture difference matters more than the syntax.
PagerDuty vs Opsgenie: Choosing an Incident Alerting Tool
Both page the right person at 3am and both integrate with everything. The real differences show up in pricing structure, workflow depth, and who already owns the ecosystem around you.
You might have missed
Evergreen posts worth revisiting.