DevOps & Platform
10 min readAugust 6, 2026Updated August 19, 2026

Terragrunt vs Terraform: What It Solves, and What Stopped Being a Problem

AJ
Ajeet Yadav
Platform & Cloud Engineer
Terragrunt vs Terraform: What It Solves, and What Stopped Being a Problem

Quick answer

Terragrunt exists because of things Terraform refused to do — variables in backend blocks, dependency ordering across root modules, DRY configuration. One of those gaps has now closed in OpenTofu. Here's what Terragrunt still genuinely buys you, and the cost nobody puts in the comparison table.

10 min read · DevOps & Platform

Terragrunt is a thin wrapper around Terraform and OpenTofu that exists because of specific things those tools would not do. It is not an alternative to Terraform — it calls Terraform. Every terragrunt apply ends with a terraform apply in a generated working directory.

That framing matters because the honest version of this comparison is not "which is better." It is: which of Terragrunt's original justifications still apply to you, and is the abstraction worth its cost? At least one of those justifications has recently stopped being a problem, and the ecosystem has grown alternatives for several others.

The problems Terragrunt was built to solve

1. You cannot put variables in a backend block. This was the original sin. Terraform's backend configuration is evaluated before variables exist, so you cannot write bucket = "tfstate-${var.environment}". With twelve environments you either hardcode twelve backend blocks or pass -backend-config flags from a wrapper script. Terragrunt was that wrapper script, productised.

2. There is no dependency ordering between root modules. Within a configuration Terraform builds a dependency graph. Across separate root modules — VPC, then cluster, then applications, each with its own state — there is nothing. You run them in the right order yourself, by hand or by script.

3. Configuration repeats across environments. Dev, staging and production differ in perhaps eight values and share four hundred lines. Terraform modules solve this for resources, but the root-module boilerplate around them — backend, providers, versions — still gets copied.

4. Applying many modules is manual. Twenty-five root modules means twenty-five terraform apply runs, in dependency order.

What Terragrunt provides

Configuration lives in terragrunt.hcl files, one per unit of deployment. The available blocks:

BlockPurpose
includeInherit configuration from a parent file
dependencyDepend on another unit and read its outputs
dependenciesDeclare ordering without reading outputs
generateWrite files into the working directory before running
remote_stateConfigure the backend, with full expression support
terraformConfigure the wrapped tool, including source
localsReusable local values
errorsRetry and ignore rules for known-transient failures
featureFeature flags in HCL
excludeDynamically exclude units from a run
unit / stackDeployment units and collections, in stack files

The pattern that sells it

A root configuration, conventionally root.hcl, holds what every unit shares:

hcl
1remote_state {
2  backend = "s3"
3  generate = {
4    path      = "backend.tf"
5    if_exists = "overwrite_terragrunt"
6  }
7  config = {
8    bucket         = "my-tfstate-${local.env}"
9    key            = "${path_relative_to_include()}/terraform.tfstate"
10    region         = "eu-west-1"
11    encrypt        = true
12    dynamodb_table = "tf-locks"
13  }
14}

Note key = "${path_relative_to_include()}/terraform.tfstate". Each unit's state path is derived from its position in the directory tree. Add a new unit anywhere and its state key is correct with no configuration — that is the DRY win, and it is a real one.

Each unit then inherits it:

hcl
1include "root" {
2  path = find_in_parent_folders("root.hcl")
3}
4
5terraform {
6  source = "git::[email protected]:acme/modules.git//eks?ref=v1.4.0"
7}
8
9dependency "vpc" {
10  config_path = "../vpc"
11}
12
13inputs = {
14  vpc_id     = dependency.vpc.outputs.vpc_id
15  subnet_ids = dependency.vpc.outputs.private_subnet_ids
16  cluster_name = "prod-eks"
17}

Two details worth getting right:

Label your include blocks. include "root" { ... } rather than bare include { ... }. Bare includes are supported for backward compatibility but the usage is deprecated, and labels are required once you have more than one include, since each must be uniquely named.

dependency is the feature that does not exist in Terraform. ../vpc is a separate root module with its own state file. Terragrunt reads its outputs, wires them in, and — critically — knows to apply the VPC first. Then terragrunt run --all apply walks the whole tree in dependency order.

That last capability is the single strongest argument for Terragrunt. Nothing in Terraform does it.

What has changed

Two of the four original justifications have weakened.

Backend variables now work in OpenTofu

OpenTofu 1.8 introduced early variable evaluation, which lifted the restriction that started all of this. Variables and locals can be used in backend configuration, module sources, and encryption configuration, provided they do not depend on resources, data sources, or module outputs. tofu init reads your .tfvars and accepts -var and -var-file, and a variable can be declared const = true to assert it must be computable without state.

If you are on OpenTofu and Terragrunt's appeal to you was primarily "I can finally template my backend," that is now available without a wrapper. This does not eliminate dependency or run --all, but it removes the objection that historically got Terragrunt adopted in the first place. Check what your specific tool and version support rather than assuming the old restriction still holds.

CI/CD platforms took over orchestration

Atlantis, Spacelift, env0, Terraform Cloud and similar tools now handle much of what run --all was for: knowing which modules changed, applying them in order, gating on approvals, and showing plans on pull requests. If you already run one, Terragrunt's orchestration overlaps with it — and two orchestrators is worse than one.

Terraform Day-2 Operations Checklist

State hygiene, drift, imports, policy checks, and upgrade routines — everything after `terraform apply` works. Plain Markdown, commit it to your repo.

Free. Instant download. You'll also get the occasional deep-dive from the newsletter — unsubscribe anytime.

The cost nobody lists

Comparison tables tend to list Terragrunt's benefits and stop. The costs are real.

Another tool, another version matrix. Terragrunt version, Terraform or OpenTofu version, provider versions, module versions. Terragrunt releases track upstream releases, and a mismatch produces errors that read like Terraform errors but are not.

Debugging happens through two layers. Terragrunt generates a working directory, generates files into it, and runs Terraform there. When something is wrong, the error is about generated code you did not write, in a directory you did not choose. terragrunt render and the --log-level flags help. The experience is still meaningfully worse than plain Terraform.

The abstraction can hide too much. A well-built Terragrunt tree is elegant: eight lines per environment, everything inherited. It is also a tree where a new engineer cannot tell what will actually be applied without running a Terragrunt command, because the configuration is assembled from four files across three directory levels. Elegance and legibility trade off here, and teams routinely over-rotate toward elegance.

Hiring and onboarding. Terraform knowledge is common. Terragrunt knowledge is not. Every new engineer learns your directory conventions before they can change anything.

run --all apply is a genuinely dangerous command. It applies everything in the tree. On a wide tree, in production, that is a very large blast radius behind a short command. Teams that use it habitually eventually have a bad day.

When Terragrunt is worth it

Many environments with identical structure. Six environments across three regions, same architecture. The DRY win compounds, and the state-key convention alone justifies it.

Genuinely separate root modules with real dependencies. If your infrastructure is deliberately split into many small state files — which is good practice; it limits blast radius and speeds plans — then cross-module dependency ordering is a real problem, and dependency is the cleanest solution available.

No orchestration platform. If you are not running Spacelift or Atlantis, Terragrunt gives you ordering and bulk operations for free.

You want small state files and refuse to hand-order applies. This is the sweet spot, and it is a legitimate architectural position.

When to skip it

A single environment, or two. The DRY savings do not cover the added tool. Duplicate the backend block. It is fine.

One large root module. If everything is in one state file, Terraform's own dependency graph already orders your resources. dependency solves nothing.

You already run an orchestration platform. Do not stack two orchestrators.

You are on OpenTofu and backend templating was the whole reason. As above — that gap has closed.

Your team is new to Terraform. Learn Terraform properly first. Terragrunt's abstractions make sense once you have felt the problems they solve; before that they are unexplained ceremony.

What to do instead, if you skip it

The plain-Terraform patterns that cover most of the same ground:

A directory per environment, with a shared module. environments/prod/main.tf is thin — a backend block and one module call. The module holds the real configuration. Duplication is confined to backend blocks and a handful of variables.

A thin wrapper script for ordering. A twenty-line Makefile or shell script that applies modules in a fixed order handles most real dependency needs. It is less capable than Terragrunt's graph and vastly easier for anyone to read.

Avoid workspaces for environments. This is the anti-pattern worth naming. Terraform workspaces share one backend configuration and one module tree, which means production and dev differ only by a variable and a state key. A missed terraform workspace select applies dev's plan to production, and the safeguards are conventions rather than structure. Use directories.

Frequently Asked Questions

Is Terragrunt a replacement for Terraform?

No. It is a wrapper that generates configuration and invokes Terraform or OpenTofu. You still write Terraform modules, still use providers, and still get Terraform state. Terragrunt manages the layer above: backend configuration, cross-module dependencies, and bulk execution.

Does Terragrunt work with OpenTofu?

Yes — the terraform block configures interaction with OpenTofu or Terraform, and Terragrunt supports both. Worth noting is that OpenTofu 1.8's early variable evaluation removes one of Terragrunt's historical justifications, so the combination is a smaller win than it once was.

Should I use run --all apply in CI?

Very carefully, if at all. It applies every unit in the tree in dependency order, which is a large blast radius behind a short command. Safer patterns are to scope it to a subdirectory, apply only units whose inputs changed, or require an explicit plan review per unit. Treat it as a power tool, not a default.

Can I migrate away from Terragrunt later?

Yes, and it is more tractable than most abstraction removals, because the generated output is ordinary Terraform. terragrunt render shows you the assembled configuration for a unit, which becomes the starting point for a plain root module. The state files themselves are standard and do not need conversion. What you rebuild is ordering and backend templating.

What is the difference between dependency and dependencies?

dependency creates an ordering relationship and exposes the other unit's outputs, so you can pass a VPC ID into a cluster. dependencies only declares ordering, with no output access — use it when a unit must run after another but does not consume anything from it.

Do I need Terragrunt for a monorepo of Terraform modules?

No. Modules and a monorepo are orthogonal to Terragrunt. Terragrunt becomes relevant when you have many root modules — separate state files — that depend on each other. A repository full of reusable child modules called from a few root modules does not need it.

Is Terragrunt still actively maintained?

Yes, and it has continued adding capability — errors blocks for retry and ignore rules, feature flags, exclude for dynamic filtering, and unit/stack blocks for composing collections of deployments. The question for most teams is not whether it is maintained but whether their setup is large enough to need it.

See also

Official References

Was this article helpful?

Be the first to rate this article

Related Topics

Terragrunt
Terraform
OpenTofu
IaC
DRY
Platform Engineering
DevOps

Found this useful? Share it.

Practice this

Related tools

Read Next