Terragrunt is a thin wrapper around Terraform and OpenTofu, built by Gruntwork. Every plan and apply still runs through the terraform or tofu binary underneath. What Terragrunt adds is orchestration across many small root modules (a root module is the directory where you run terraform plan, with its own state file). It writes the backend configuration for each one, passes outputs from one to the next, runs commands across all of them in dependency order, and runs your scripts before and after each command.
The short verdict: you need Terragrunt when you manage dozens of root modules spread over accounts and regions, and the copied backend blocks and cross-module lookups have become your main source of mistakes. With a handful of root modules, plain Terraform or OpenTofu with shared modules is simpler. Terragrunt 1.0 shipped on March 30, 2026 with a compatibility promise for its CLI flags and configuration, so it is a safer tool to pin than it was during the 0.x years.
What Terragrunt Adds to Terraform
Terragrunt has two terms worth learning first. A unit is a directory containing a terragrunt.hcl file, and it represents a single piece of infrastructure, such as a VPC or a database. A stack is a collection of units managed together, such as everything that makes up staging.
- One backend definition for every unit. Terraform backend blocks cannot refer to variables or locals, so each root module carries its own hard-coded copy, with a unique state key typed in. Terragrunt's
remote_stateblock lives once in a parent file, builds each unit's key from its folder path withpath_relative_to_include(), and writes abackend.tffile into the unit before Terraform runs. Withterragrunt backend bootstrap, it can also create the S3 bucket and DynamoDB lock table if they do not exist.
- Generated files. A
generateblock writes any file into a unit before each run, which teams use to stamp the same provider configuration into every unit.
- Dependencies between units. A
dependencyblock reads another unit's outputs, so the app unit can takevpc_idfrom the VPC unit.mock_outputssupplies placeholder values so you can plan before the dependency has been applied.
- Runs across many units.
terragrunt run --all planruns a command in every unit under the current directory, ordered by the dependency graph and in parallel where the graph allows. In 1.0, theruncommand replaced the olderrun-all.
- Explicit stacks. A
terragrunt.stack.hclfile listsunitblocks with a source, a path, and values.terragrunt stack generatewrites the units into a.terragrunt-stackdirectory, andterragrunt stack run applydeploys them.
- Hooks.
before_hook,after_hook, anderror_hookrun commands around Terraform commands, for example a linter before everyplan.
What Terraform Covers on Its Own
Terraform has closed part of the gap since Terragrunt first appeared.
- Modules package reusable code, so each environment's root module can call the same module with different inputs.
- CLI workspaces keep several named states for one configuration in one backend. HashiCorp says workspaces are not appropriate for system decomposition or deployments requiring separate credentials and access controls, so they rarely replace per-environment folders.
- The
terraform_remote_statedata source reads outputs from another root module's state, which is what Terragrunt'sdependencyblock does for you.
- Partial backend configuration leaves values out of the backend block and passes them at
terraform initwith-backend-config, usually from a pipeline script.
- Terraform Stacks, generally available in HCP Terraform (HashiCorp's hosted service), organize modules into components (
.tfcomponent.hclfiles) and repeat them as deployments (.tfdeploy.hclfiles), each with its own isolated state. The documentation describes Stacks as an HCP Terraform feature, so they help only if you run there.
- OpenTofu 1.8 added early evaluation of variables and locals in backend configuration and module sources, which removes one of the oldest reasons to wrap. See OpenTofu vs Terraform.
The Same Unit in Plain Terraform and in Terragrunt
Here is a staging app that needs the VPC ID from a separate VPC root module, in plain Terraform:
# live/stage/app/main.tf
terraform {
backend "s3" {
bucket = "acme-tofu-state"
key = "stage/app/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "acme-lock-table"
}
}
data "terraform_remote_state" "vpc" {
backend = "s3"
config = {
bucket = "acme-tofu-state"
key = "stage/vpc/terraform.tfstate"
region = "us-east-1"
}
}
module "app" {
source = "git::git@github.com:acme/modules.git//app?ref=v0.0.3"
vpc_id = data.terraform_remote_state.vpc.outputs.vpc_id
instance_count = 3
instance_type = "t4g.micro"
}
Production gets a copy of this file with stage changed to prod in two keys. Miss one, and prod reads staging's VPC. The Terragrunt version puts the backend in one parent file:
# live/root.hcl
remote_state {
backend = "s3"
generate = {
path = "backend.tf"
if_exists = "overwrite"
}
config = {
bucket = "acme-tofu-state"
key = "${path_relative_to_include()}/tofu.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "acme-lock-table"
}
}
Each unit then holds only what is different about it:
# live/stage/app/terragrunt.hcl
include "root" {
path = find_in_parent_folders("root.hcl")
}
terraform {
source = "git::git@github.com:acme/modules.git//app?ref=v0.0.3"
}
dependency "vpc" {
config_path = "../vpc"
}
inputs = {
vpc_id = dependency.vpc.outputs.vpc_id
instance_count = 3
instance_type = "t4g.micro"
}
find_in_parent_folders searches up the directory tree for root.hcl, so every unit inherits the same backend, and the state key follows the folder path. Ten environments means ten small files and one backend definition, and terragrunt run --all apply from live/stage applies the VPC before the app.
What Terragrunt Costs
- Another tool to install and pin. Every laptop and CI runner needs matching Terragrunt and Terraform or OpenTofu versions.
- More layers to debug. Terragrunt copies module code into a
.terragrunt-cachefolder and writes generated files there. A failure can come from the include chain, a generatedbackend.tf, or the module itself.
- Mock outputs can mislead. A plan built on placeholder values does not show what the real apply will do.
- Wide applies.
run --all applycan change many units in one command. It asks for confirmation, and--non-interactiveskips that prompt, which is common in CI.
- More to teach. New engineers learn Terragrunt functions and folder conventions on top of Terraform itself.
Terragrunt or Plain Terraform: Decision Table
| Your situation | Plain Terraform or OpenTofu | Terragrunt |
|---|---|---|
| A few root modules, one or two environments | Simpler, fewer moving parts | Extra layer with little payoff |
| Same modules across many accounts, regions, environments | Copied backend blocks or -backend-config scripts | One remote_state block, keys from folder paths |
| Outputs passed between root modules | terraform_remote_state with typed-in keys | dependency blocks with ordering built in |
| Applying many root modules in order | Ordering written into pipeline scripts | run --all follows the dependency graph |
| Already on HCP Terraform | Terraform Stacks cover repetition | Optional |
| On OpenTofu 1.8 or later | Variables allowed in backend config | Still useful for dependencies and run --all |
Crossplane vs Terraform
Crossplane answers a different question. It is a control plane framework, a graduated Cloud Native Computing Foundation project, that runs inside Kubernetes. You declare cloud resources as managed resources: Kubernetes objects whose spec.forProvider describes a real cloud resource, such as an EC2 instance. A Crossplane provider creates the resource and keeps checking it, and Crossplane overrides any changes made to it outside Crossplane. Compositions let a platform team publish its own APIs, such as a Database type that creates an RDS instance, subnet group, and security group together.
Terraform changes infrastructure only when someone runs plan and apply. Drift sits until the next plan shows it, and a person reads the diff before anything changes.
- Choose Crossplane when Kubernetes is already your platform hub, you want app teams to request infrastructure through kubectl or GitOps, and continuous correction is the behavior you want.
- Choose Terraform or OpenTofu when you want a reviewable plan before every change, the widest provider coverage, and no cluster to operate for your infrastructure tooling.
One trade-off decides it for many teams: Crossplane reverts an emergency console fix on its next check, while Terraform leaves it in place and shows it in the next plan.
Running Many Terraform Projects in ops0
Terragrunt usually arrives once per-environment backends and cross-project outputs pile up into wiring someone has to keep straight. ops0 takes over that wiring for the engineers who own the modules, while the code stays plain Terraform, OpenTofu, or CloudFormation in your Git repository.
- Any engine. Each project in ops0 runs Terraform, OpenTofu, Oxid (ops0's open-source engine with PostgreSQL state), or AWS CloudFormation.
- State backends in a click. Each project gets its own backend: Amazon S3 with locking, Google Cloud Storage, Azure Blob Storage, or Oracle Object Storage, in your own bucket or managed by ops0. State can live in a different account or cloud from the resources it tracks.
- One variable hierarchy. Org variables apply to all projects or selected ones, and project values override them. Secrets are pulled from AWS Secrets Manager, Google Secret Manager, Azure Key Vault, or OCI Vault at deploy time, and the effective view shows where every value came from.
- An org-wide dependency map. ops0 shows which projects consume which outputs, which share a variable with conflicting values, and which hard-code a resource ID another project manages, with a jump to the file and line behind each link.
- Output references. Pick another project's output, and ops0 generates the remote-state block for you.
Every deployment still goes through plan review, policy checks, and an approval step where the person who starts a deployment cannot approve it. See IaC in ops0 and Deployments.
Does Terragrunt replace Terraform?
No. Terragrunt calls the terraform or tofu binary for every plan and apply. It adds a layer on top: shared backend configuration, dependency blocks that pass outputs between units, run --all to run many units in order, and hooks around each command.
Is Terragrunt still needed with Terraform Stacks?
If you run on HCP Terraform, Stacks cover much of the same need: components built from modules, repeated as deployments with isolated state. Stacks are documented as an HCP Terraform feature, so teams running Terraform or OpenTofu elsewhere still use Terragrunt or pipeline scripts for the same job.
What is a unit in Terragrunt?
A unit is a directory containing a terragrunt.hcl file, and it represents a single piece of infrastructure, such as a VPC or a database. A stack is a collection of units managed together, defined either by folder layout or in a terragrunt.stack.hcl file.
Does Terragrunt work with OpenTofu?
Yes. Terragrunt runs either OpenTofu or Terraform, and its own documentation uses OpenTofu in most examples. OpenTofu 1.8 also allows variables in backend configuration, which removes one reason to adopt Terragrunt, though dependency ordering and run --all still apply.
What is the difference between Crossplane and Terraform?
Crossplane runs inside Kubernetes and keeps checking cloud resources, reverting changes made outside it to match the declared spec. Terraform changes resources only when someone runs plan and apply, and shows the diff for review first. Crossplane fits Kubernetes-centered platform teams; Terraform fits teams that want a reviewed plan before every change.
- State Backend: Terragrunt. remote_state, generated backend.tf, path_relative_to_include, and backend bootstrap.
- Explicit Stacks: Terragrunt. terragrunt.stack.hcl, unit blocks, stack generate and stack run.
- Terragrunt 1.0 Released: Gruntwork. 1.0 compatibility promise and run replacing run-all.
- Backend block configuration: HashiCorp. Backend blocks cannot refer to variables; partial configuration.
- Terraform Stacks: HashiCorp. Components, deployments, and isolated state in HCP Terraform.
- What's Crossplane?: Crossplane. Control plane model, drift correction, and CNCF status.