Terragrunt executes the OpenTofu engine via the --tofu flag. This episode designs a DRY multi-environment architecture for Dev, Staging, and Prod with backends and providers centralized in a single root terragrunt.hcl.

In the previous episode 11 we installed three layers of quality protection: tofu fmt, tofu validate, static analysis with tflint/Checkov/Trivy, and the native tofu test framework. Now imagine your code is tidy and tested — but the repo has three environment folders (Dev, Staging, Prod), each with an S3 backend, provider block, and nearly identical configuration. Every time the team adds a new environment, they copy-paste dozens of lines. Every time the backend configuration changes, they must touch dozens of files. This is the problem Terragrunt was born to solve: duplication is the root of configuration drift.
In episode 12 we'll integrate OpenTofu with Terragrunt. We'll learn how Terragrunt runs the tofu binary via terragrunt --tofu, then arrange a DRY (Don't Repeat Yourself) architecture for multi-environment setups with backends and providers centralized in one root file. In the end, adding a new environment is just a small folder containing environment-specific values — not a large copy.
Imagine a team with a dev/, staging/, and prod/ architecture. In every folder there's a backend "s3" block, a provider "aws" configuration, and a call to the same module. Consistency is maintained only by human discipline — and human discipline always leaks. Someone changes the region in prod but forgets dev, or adds a new argument to only one environment. tofu plan only catches it once the problem has already spread.
Terragrunt is a thin wrapper that runs the IaC engine behind it and offers inheritance: identical configuration is written once at the root, then inherited by every environment. Backends, providers, and state locations only need to be defined once; each environment folder only holds what actually differs — its inputs values.
By default Terragrunt calls the terraform binary. To use it with OpenTofu, use the --tofu flag — Terragrunt will look for the tofu binary in PATH and run the entire lifecycle (init, plan, apply) through it. All Terragrunt features keep working fully: inheritance, run-all to execute many units at once, and dependency for cross-unit references.
# Tell Terragrunt to use the tofu binary for this command
terragrunt --tofu plan
terragrunt --tofu apply
# Run all environments at once from the root folder
terragrunt run-all plan
# Set a permanent default so the --tofu flag isn't needed constantly
export TERRAGRUNT_TF_PATH=/usr/local/bin/tofuTip
Set TERRAGRUNT_TF_PATH in ~/.terragruntrc or a CI environment so every team member and pipeline automatically uses OpenTofu — a single source of truth for the engine, rather than depending on who remembers to type the --tofu flag.
This is the pattern thousands of production teams use. The repository splits into two parts: a root holding everything that's the same, and environment folders that only hold what differs.
infrastructure-live/
├── terragrunt.hcl # root: centralized backend & provider
├── dev/
│ └── vpc/
│ └── terragrunt.hcl # only environment-specific inputs
├── staging/
│ └── vpc/
│ └── terragrunt.hcl
└── prod/
└── vpc/
└── terragrunt.hclThe root file defines remote_state (S3 backend + locking) and a generate "provider" that inserts a provider block into every unit automatically:
locals {
env = path_relative_to_include()
account = get_aws_account_id()
region = "ap-southeast-1"
}
remote_state {
backend = "s3"
config = {
bucket = "opentofu-state-${local.account}"
key = "${local.env}/terraform.tfstate"
region = local.region
encrypt = true
}
generate = {
path = "backend.tf"
if_exists = "overwrite"
}
}
generate "provider" {
path = "provider.tf"
if_exists = "overwrite"
contents = <<EOF
provider "aws" {
region = "${local.region}"
}
EOF
}Notice how values like bucket and key are computed from locals — every environment automatically gets the same bucket but a different state key (dev/, staging/, prod/). Backend configuration is written once and applied everywhere.
The file in each environment is far leaner. It inherits the root with include, points to the module in use, and only provides the specific inputs:
include "root" {
path = find_in_parent_folders()
}
terraform {
source = "git::https://github.com/acme/infra-modules.git//vpc?ref=v2.1.0"
}
inputs = {
name = "vpc-${path_relative_to_include()}"
cidr_block = "10.0.0.0/16"
dns_hostnames = true
tags = {
Environment = path_relative_to_include()
}
}Adding a staging and prod environment now is just copying the dev/ folder and changing the CIDR and tags — without touching the backend or provider configuration at all. path_relative_to_include() makes names and tags follow the folder location automatically, so naming consistency no longer depends on memory.
Warning
Don't rely on modules that always point to the main branch. Pin source to a version tag like ?ref=v2.1.0 — otherwise, one push to main could change the behavior of every environment outside human review. Deterministic configuration is a prerequisite for everything we'll build in the upcoming CI/CD episodes.
When units need each other — for instance, database consuming outputs from the vpc unit — Terragrunt provides the dependency block that reads another unit's state output and schedules execution in order. Combined with terragrunt run-all plan, which runs all units in the right order from a single command at the root, managing dozens of multi-environment stacks feels like managing one.
In episode 12 we:
terragrunt --tofu and TERRAGRUNT_TF_PATH.terragrunt.hcl, environments containing only specific inputs.path_relative_to_include() and get_aws_account_id() to compute values without hardcoding.dependency and run-all for orchestrating many units.This DRY structure only shows its strength when combined with automation. In the next episode, episode 13, we'll build CI/CD pipeline automation on GitHub Actions and GitLab CI: the official opentofu/setup-opentofu action, a full GitOps flow from PR, fmt, validate, security scan, tofu plan with PR comments, to an approval gate and tofu apply via OIDC. See you there!