Anyone who has ever scaled an infrastructure across multiple AWS accounts using pure Terraform knows the problem: you copy the same provider block and the same backend configuration for the tenth time. Terraform is powerful, but it has its native limits when it comes to DRY. This is where Terragrunt comes into play. But does an additional wrapper justify the increased complexity?
Note: I always write Terraform for readability, but it also applies to OpenTofu
The problem: When Terraform reaches its limits
In theory, Terraform is modular. In practice, for large projects we often end up with a directory structure in which we maintain almost identical main.tf, variables.tf and especially backend.tf files in every environment (Dev, Staging, Prod).
The main problem: Terraform doesn’t allow variables in the backend configuration.
This means that you manually enter the S3 key for your state every time. One typo and your state ends up in the wrong environment. This is exactly where Terragrunt comes in by allowing us to define configurations centrally and pass them on to all sub-modules.
The solution: Terragrunt as an architectural adhesive
Terragrunt acts as a thin wrapper that uses the terragrunt.hcl to execute Terraform commands. The highlight is the ‘include’ function.
Example: Global Backend & Provider
Imagine defining your S3 backend only once at the root level.
# root/terragrunt.hcl
remote_state {
backend = "s3"
config = {
bucket = "my-terraform-state-${get_aws_account_id()}"
key = "${path_relative_to_include()}/terraform.tfstate"
region = "eu-central-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
In your individual modules (e.g. for a VPC or an RDS) a simple two-liner is enough to inherit the entire backend.
Dependency Management: The secret killer feature
An often underestimated point is how Terragrunt handles dependencies. In vanilla Terraform you have to use terraform_remote_state data sources to read outputs from another module. This is tedious and error-prone.
In Terragrunt you simply declare a dependency block:
dependency "vpc" {
config_path = "../vpc"
}
inputs = {
vpc_id = dependency.vpc.outputs.vpc_id
}
Terragrunt understands the graph structure and knows: “I have to build the VPC first before I start the RDS module.”
Multi-account setup: From theory to practice
In an enterprise environment, you often have separate AWS accounts for Security, Shared-Services, Dev and Prod. With Terragrunt we structure this so that the account ID and region automatically flow into the modules.
The directory structure
.
├── terragrunt.hcl # Root configuration (backend & provider)
├── common.hcl # Common variables (Project Name, etc.)
├── prod
│ ├── account.hcl # Account-specific data (Account ID: 123456)
│ └── vpc
│ └── terragrunt.hcl # Specific module instance
└── dev
├── account.hcl # Account-specific data (Account ID: 789012)
└── vpc
└── terragrunt.hcl
The “Magic” module file
We don’t have to write much in dev/vpc/terragrunt.hcl now. We simply upload the configurations:
include "root" {
path = find_in_parent_folders()
}
include "account" {
path = find_in_parent_folders("account.hcl")
expose = true
}
# We pull the official module directly from GitHub or from a registry
terraform {
source="tfr:///terraform-aws-modules/vpc/aws//?version=5.0.0"
}
# Here we inject the account specific data without hardcoding
inputs = {
name = "vpc-${include.account.locals.account_name}"
cidr="10.0.0.0/16"
tags = {
AccountID = include.account.locals.aws_account_id
}
}
The conclusion: When is the wrapper worth it?
My motto: Choose your dependencies wisely.
Terragrunt is a “Go” if:
- You have more than two environments that are almost identical.
- You are running a multi-account setup and need to configure backends dynamically.
- You have dependencies between isolated state files (e.g. app needs DB output).
Stick with Vanilla Terraform if:
- Your project is small and lives in one account.
- Your team is just learning Terraform (the extra layer of abstraction can be confusing).
- You already rely heavily on Terraform Cloud/Enterprise, as these platforms go their own way to orchestration (like the new stacks).
![[EN] DRY Terraform: Multi-Account-Strategien mit Terragrunt in der Praxis](/images/Terraform-und-Terragrunt.jpeg)