Skip to content
hannctl
PDF

case-studies aws-terragrunt DESIGN.md

AWS Terragrunt DRY IaC Design and implementation

Sixteen Terraform modules behind one root config, with Terragrunt running them in order from their dependency graph.

iam_custom_policyiam_useriam_custom_roleiam_groupvpcelastic_ipebsinternet_gatewaysecurity_groupvpc_endpointvpc_peeringelasticache_subnet_groupnat_gatewayroute_tableinstance_profileec2_instance
live/root.hclbackend · provider · common inputslive/dev/account.hclPer-account values<region>/region.hclPer-region values_envcommon/<module>.hclsource · dependency · mock_outputs<resource>/terragrunt.hclStates only its own inputsbackend.tfprovider.tf1 root config replaces 16 copies
Terragrunt generates backend.tf and provider.tf. One root config replaces 16 copies.
terragrunt: module version upgrade runbook$ terragrunt state pull > backup.tfstate$ find . -name '.terragrunt-cache' -type d -prune -exec rm -rf {} +# terraform.source를 새 버전으로 변경$ terragrunt init$ terragrunt state push backup.tfstate$ terragrunt plan# Back up state# Clear caches# The only code change# Download the new module# Restore state only if needed# Must show no changesNo changes.
The upgrade is done only when plan prints No changes. If it shows a change, stop and read the diff.

The provider generated from the root

What this code does: Terragrunt’s generate block writes a provider.tf file into each unit folder. The region comes from the region settings, the Environment tag from the account name, and every resource automatically gets a ManagedBy = terragrunt tag. The last line merges the account and region locals into the common inputs of every unit, so a unit file keeps only the values specific to that unit.

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: Declares that this unit depends on the elastic_ip unit in the neighboring folder. From this, Terragrunt runs elastic_ip first and passes its output (eip_id) into this unit as input. When no real output exists yet, the fake IDs in mock_outputs are used, but only for init, validate, and plan, so the whole stack can be validated without a cloud while fake values can never reach a real apply.

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"]
}
© 2026 Kim JunHyun · Built with Astro · The infrastructure behind this site is code too

↑↓ move · Enter open · Esc close

Choose a theme

Click a badge to switch to that theme on the same content. Dragging only swings it.

Back to the front page →