PF-04 · AWS · Terragrunt
AWS Terragrunt DRY IaC
Sixteen Terraform modules behind one root config, with Terragrunt running them in order from their dependency graph.
- Role
- Design and implementation
- Period
- Personal project · released Nov 2026
- Cloud
- AWS
- Scale
- 16 modules · 16 units · 4 dependency levels
- Key tech
- Terragrunt · Terraform · AWS
- 16
- Terraform modules and units
- 1
- Root config replacing 16 copies
- - 40 %
- Deployment time (production result, not measured in this repository)
At a glance
Problem
Each Terraform root repeated its backend and provider blocks, and the order between roots lived in people's heads.
Approach
Modules stay plain Terraform and Terragrunt wraps them. The backend, provider, and common inputs are generated once from live/root.hcl. _envcommon holds module sources and dependencies, and each unit states only its inputs. dependency blocks build the graph, and mock_outputs let the whole stack validate without a cloud account.
Result
This repository groups 16 modules and 16 units into four dependency levels and validates them without cloud credentials. The 40% shorter deployment time is a production result from moving a live estate to this pattern, and was not measured in this repository.
Up close
The provider generated from the root
What this code does: Generates the provider and common inputs at the root, so no unit repeats them.
generate "provider" {
path = "provider.tf"
if_exists = "overwrite_terragrunt"
contents = <<PROVIDER
provider "aws" {
region = "${local.aws_region}"
default_tags {
tags = {
Environment = "${local.account_name}"
ManagedBy = "terragrunt"
}
}
}
PROVIDER
}
inputs = merge(local.account_vars.locals, local.region_vars.locals)
dependency and mock_outputs
What this code does: Connects outputs between modules and validates with mock values before plan.
dependency "elastic_ip" {
config_path = "${get_terragrunt_dir()}/../elastic_ip"
mock_outputs = {
eip_id = {
"demo-eip-1" = "eipalloc-00000000000000001"
"demo-eip-2" = "eipalloc-00000000000000002"
}
}
mock_outputs_allowed_terraform_commands = ["init", "validate", "plan"]
}What I learned
Terragrunt does not replace Terraform. It organizes the code. Writing dependencies as code removes the need to track run order by hand.
What I would improve
State is local so the demo stays self-contained. A team should move to the S3 backend shown in root.hcl. us-east-1 holds only a region file, and units are validated in CI but not applied.