Beyond Terraform — Using Terragrunt to Manage Infrastructure at Scale
Beyond Terraform — Using Terragrunt to Manage Infrastructure at Scale”
Terraform is excellent at provisioning infrastructure, but large Terraform codebases tend to accumulate problems:
- Copy‑pasted backend configuration\
- Repeated provider configuration\
- Environment drift across dev/stage/prod\
- Dependency ordering between stacks\
- Painful module orchestration
Terragrunt solves these operational problems without replacing Terraform. It wraps Terraform and adds features for composition, DRY configuration, and orchestration of infrastructure stacks.
This guide explains how experienced engineers actually structure Terragrunt in production environments.
What Terragrunt Really Does
Terragrunt is a thin wrapper around Terraform that adds operational capabilities Terraform intentionally avoids.
Capability Why It Matters ———————————– ———————————– DRY Terraform configuration Avoid copy‑pasting backend and providers
Environment hierarchy Clean dev/stage/prod structure
Dependency management Apply stacks in correct order
Remote state automation Automatically configure state
Module orchestration Run apply across multiple modules
———————————————————————–
Terraform itself remains responsible for infrastructure provisioning. Terragrunt focuses on codebase management and orchestration.
Typical Terraform Scaling Problem
A real infrastructure repository often starts simple and gradually becomes messy.
terraform/
├── dev
│ ├── vpc
│ ├── eks
│ └── rds
├── stage
│ ├── vpc
│ ├── eks
│ └── rds
└── prod
├── vpc
├── eks
└── rds
Each directory typically contains:
- backend configuration
- provider configuration
- repeated variables
- identical module references
As the infrastructure grows, copy‑paste drift becomes inevitable.
Terragrunt Repository Layout
Terragrunt separates infrastructure modules from environment configuration.
infrastructure-live/
├── dev
│ ├── vpc
│ │ └── terragrunt.hcl
│ ├── eks
│ │ └── terragrunt.hcl
│ └── rds
│ └── terragrunt.hcl
├── stage
└── prod
Terraform modules live in a separate repository:
infrastructure-modules/
├── vpc
├── eks
└── rds
Terragrunt orchestrates module usage across environments.
Installing Terragrunt
brew install terragrunt
Or download the binary:
https://terragrunt.gruntwork.io/docs/getting-started/install/
Verify installation:
terragrunt --version
Minimal Terragrunt Configuration
Example terragrunt.hcl:
terraform {
source = "git::ssh://git@github.com/company/infrastructure-modules.git//vpc"
}
inputs = {
cidr_block = "10.0.0.0/16"
}
Execution:
terragrunt apply
Terragrunt downloads the module and executes Terraform internally.
The Most Important Feature: include
Large infrastructures require shared configuration. Terragrunt solves
this with inheritance via include.
Root configuration:
infrastructure-live/terragrunt.hcl
Example:
remote_state {
backend = "s3"
config = {
bucket = "company-terraform-state"
key = "${path_relative_to_include()}/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
Child module:
dev/vpc/terragrunt.hcl
include {
path = find_in_parent_folders()
}
terraform {
source = "git::ssh://git@github.com/company/infrastructure-modules.git//vpc"
}
inputs = {
cidr_block = "10.0.0.0/16"
}
All modules inherit shared configuration automatically.
Remote State Without Duplication
Terraform normally requires each module to define its backend.
Terragrunt can generate it automatically.
remote_state {
backend = "s3"
generate = {
path = "backend.tf"
if_exists = "overwrite"
}
config = {
bucket = "company-terraform-state"
key = "${path_relative_to_include()}/terraform.tfstate"
region = "us-east-1"
}
}
When Terragrunt runs, backend.tf is generated dynamically.
Environment Configuration
Terragrunt supports environment‑level variables.
live/
├── terragrunt.hcl
├── dev
│ └── env.hcl
├── stage
│ └── env.hcl
└── prod
└── env.hcl
Example env.hcl:
locals {
env = "dev"
}
Load it in a module:
locals {
env_config = read_terragrunt_config(find_in_parent_folders("env.hcl"))
}
inputs = {
environment = local.env_config.locals.env
}
This allows environment‑aware configuration without repeating variables.
Managing Dependencies
Infrastructure stacks often depend on one another.
Example:
VPC -> EKS -> Application
Terragrunt provides a dependency block.
dependency "vpc" {
config_path = "../vpc"
}
inputs = {
vpc_id = dependency.vpc.outputs.vpc_id
}
Terragrunt reads outputs from Terraform state automatically.
Running Multiple Modules
Instead of applying each module individually:
cd vpc
terraform apply
cd eks
terraform apply
Use Terragrunt orchestration:
terragrunt run-all apply
Terragrunt:
- Builds a dependency graph
- Applies modules in correct order
- Parallelizes independent stacks
Module Version Pinning
Modules are typically referenced from Git.
terraform {
source = "git::ssh://git@github.com/company/infrastructure-modules.git//eks?ref=v1.4.0"
}
Benefits:
- controlled upgrades
- reproducible infrastructure
- easy rollback
Provider Generation
Provider configuration can also be generated.
generate "provider" {
path = "provider.tf"
if_exists = "overwrite"
contents = <<EOF
provider "aws" {
region = "us-east-1"
}
EOF
}
This prevents provider duplication across modules.
Terragrunt Repository Design (Production Layout)
Most teams eventually converge on a layered layout.
repo-root
├── infrastructure-modules
│ ├── vpc
│ ├── eks
│ └── rds
│
└── infrastructure-live
├── terragrunt.hcl
│
├── dev
│ ├── env.hcl
│ ├── vpc
│ ├── eks
│ └── rds
│
├── stage
│ └── ...
│
└── prod
└── ...
Responsibilities:
Modules repository:
- reusable Terraform modules
- versioned releases
- no environment values
Live repository:
- environment configuration
- Terragrunt orchestration
- module version pinning
Multi‑Account / Multi‑Region Pattern
Terragrunt scales well to multi‑account architectures.
Example:
live/
├── prod
│ ├── account.hcl
│ ├── us-east-1
│ │ ├── vpc
│ │ └── eks
│ └── us-west-2
│ └── vpc
Example account.hcl:
locals {
account_id = "123456789012"
}
Provider configuration can dynamically assume roles based on this configuration.
CI/CD Integration
Terragrunt works well inside pipelines.
Example CI stage:
terragrunt run-all plan --terragrunt-non-interactive
Typical automation tools:
- Atlantis
- Spacelift
- GitHub Actions
- GitLab CI
- Azure DevOps
When Terragrunt Is Worth Using
Terragrunt becomes valuable when:
- Terraform repositories exceed ~10 modules
- multiple environments exist
- backend duplication becomes painful
- stack dependencies grow complex
For very small infrastructures, plain Terraform is simpler.
Key Takeaways
Terragrunt does not replace Terraform.
It solves the operational scaling problems Terraform intentionally avoids.
The most valuable capabilities are:
- DRY configuration
- dependency orchestration
- environment hierarchy
- remote state automation
- multi‑module execution
These features allow Terraform infrastructures to scale without turning into copy‑paste chaos.
Leave a comment