B+
infrastructureterragrunt toolkit

Terragrunt Generator

Comprehensive toolkit for generating best-practice Terragrunt configurations (HCL files) following current standards and conventions. Generates terragrunt.hcl files, root configurations, child modules, stacks, and environment setups; configures remote state backends, dependency blocks, include blocks, feature flags, exclude blocks, and errors blocks; supports DRY Terraform patterns, multi-environment layouts (dev/staging/prod), and OpenTofu engine integration. Use when creating new Terragrunt projects or resources, scaffolding multi-environment infrastructure, implementing DRY Terraform wrapper configurations, setting up terragrunt.hcl files with remote state or provider config, managing module dependencies, or building infrastructure modules with Terragrunt stacks.

npx skills add github:pantheon-org/tekhne --skill infrastructure--terragrunt--generator

Philosophy

  • Choose the architecture pattern (A, B or C) before writing any file, because every later path depends on it.
  • Keep root.hcl environment-agnostic unless the pattern explicitly says otherwise; child modules read env.hcl.
  • Define shared configuration once in the root and inherit it, rather than copying it into each unit.
  • Generate from the bundled templates and references/common-patterns.md instead of writing HCL from memory.
  • A configuration is not finished until it has passed validation with terragrunt-validator.

When to Use

  • Creating a new root.hcl, child module, standalone module, stack or catalog unit.
  • Scaffolding a multi-environment layout (dev, staging, prod) with shared remote state.
  • Configuring dependency blocks with mock outputs, feature flags, exclude blocks or errors blocks.
  • Setting up remote state and provider configuration through generate blocks.
  • Migrating generated configuration to current syntax such as run --all, exclude and errors.

When Not to Use

  • Validating or debugging existing Terragrunt files: use the terragrunt-validator skill instead.
  • Writing plain Terraform modules with no Terragrunt wrapper: use terraform-generator.
  • Applying or destroying infrastructure, which this skill does not do.
  • Producing partial snippets for documentation, where full generation and validation add no value.

Overview

Generate production-ready Terragrunt configurations following current best practices, naming conventions, and security standards. All generated configurations are automatically validated.

Terragrunt 2025 Features Supported:

  • Stacks - Infrastructure blueprints with terragrunt.stack.hcl (GA since v0.78.0)
  • Feature Flags - Runtime control via feature blocks
  • Exclude Blocks - Fine-grained execution control (replaces deprecated skip)
  • Errors Blocks - Advanced error handling (replaces deprecated retryable_errors)
  • OpenTofu Engine - Alternative IaC engine support

Root Configuration Naming

RECOMMENDED: Use root.hcl instead of terragrunt.hcl for root files per migration guide.

Approach Root File Include Syntax
Modern root.hcl find_in_parent_folders("root.hcl")
Legacy terragrunt.hcl find_in_parent_folders()

Architecture Patterns

CRITICAL: Before generating ANY configuration, determine the architecture pattern and understand its constraints.

Pattern Selection

Pattern Use When Root Behavior Structure
A: Multi-Env Agnostic Multiple environments with shared root Root reads NO env files; uses static values or get_env() root.hcl + {env}/env.hcl per environment
B: Single/Env-Aware Single environment OR environment detection needed Root can parse path or read get_env() root.hcl with optional account.hcl/region.hcl
C: Centralized Vars Shared environment definitions Root is agnostic; env.hcl reads from _env/ root.hcl + _env/{env}.hcl + {env}/env.hcl

Pattern A: Multi-Environment Agnostic Root

Key principle: root.hcl does NOT read env.hcl. Child modules read env.hcl directly.

infrastructure/
├── root.hcl              # Environment-AGNOSTIC
├── dev/env.hcl           # locals { environment = "dev" }
│   └── vpc/terragrunt.hcl
└── prod/env.hcl          # locals { environment = "prod" }
    └── vpc/terragrunt.hcl

Child module pattern:

include "root" { path = find_in_parent_folders("root.hcl") }
locals { env = read_terragrunt_config(find_in_parent_folders("env.hcl")) }
inputs = { name = "${local.env.locals.environment}-vpc" }

Pattern B: Environment-Aware Root

Key principle: Root detects environment from path or environment variable.

# root.hcl
locals {
  path_parts  = split("/", path_relative_to_include())
  environment = local.path_parts[0]  # OR: get_env("TG_ENVIRONMENT", "dev")
}

Pattern C: Centralized Variables

Key principle: Each env.hcl reads from centralized _env/{env}.hcl.

# prod/env.hcl
locals {
  env_vars    = read_terragrunt_config("${get_repo_root()}/_env/prod.hcl")
  environment = local.env_vars.locals.environment
  aws_region  = local.env_vars.locals.aws_region
}

Decision: Multi-env + shared root → A | Single env / env detection → B | Centralized vars → C

Core Capabilities

1. Generate Root Configuration

Create root-level root.hcl or terragrunt.hcl with remote state, provider config, and common variables.

Read before generating: assets/templates/root/terragrunt.hcl Patterns: references/common-patterns.md → Root Configuration Patterns

Key placeholders to replace:

  • [BUCKET_NAME], [AWS_REGION], [DYNAMODB_TABLE]
  • [TERRAFORM_VERSION], [PROVIDER_NAME], [PROVIDER_VERSION]
  • [ENVIRONMENT], [PROJECT_NAME]

Root.hcl Design Principles:

  1. Environment-agnostic by default — Don’t assume env.hcl exists at root level
  2. Use static values for provider/backend region — Or get_env() for runtime config
  3. State key uses path_relative_to_include() — Automatically includes environment path
  4. Provider tags can be static — Environment-specific tags go in child modules

2. Generate Child Module Configuration

Create child modules with dependencies, mock outputs, and proper includes.

Read before generating: assets/templates/child/terragrunt.hcl Patterns: references/common-patterns.md → Child Module Patterns

Module source options:

  • Local: a relative path to the modules directory, for example "${get_repo_root()}/modules/vpc"
  • Git: "git::https://github.com/org/repo.git//path?ref=v1.0.0"
  • Registry: "tfr:///terraform-aws-modules/vpc/aws?version=5.1.0"

3. Generate Standalone Module

Self-contained modules without root dependency.

Read before generating: assets/templates/module/terragrunt.hcl

4. Generate Multi-Environment Infrastructure

Complete directory structures for dev/staging/prod.

Before generating:

  1. Determine architecture pattern (see Architecture Patterns section)
  2. Read relevant templates for root and child modules
  3. Verify env.hcl placement and access patterns

Patterns: references/common-patterns.md → Environment-Specific Patterns

Typical structure (Pattern A):

infrastructure/
├── root.hcl              # Environment-AGNOSTIC root config
├── dev/
│   ├── env.hcl           # Dev environment variables
│   └── vpc/terragrunt.hcl
└── prod/
    ├── env.hcl           # Prod environment variables
    └── vpc/terragrunt.hcl

5. Generate Terragrunt Stacks (2025)

Infrastructure blueprints using terragrunt.stack.hcl.

Read before generating: assets/templates/stack/terragrunt.stack.hcl and assets/templates/catalog/terragrunt.hcl Docs: Stacks Documentation Patterns: references/common-patterns.md → Stacks Patterns

Commands:

terragrunt stack generate    # Generate unit configurations
terragrunt stack run plan    # Plan all units
terragrunt stack run apply   # Apply all units
terragrunt stack output      # Get aggregated outputs
terragrunt stack clean       # Clean generated directories

6. Generate Feature Flags (2025)

Runtime control without code changes.

Docs: Feature Flags Documentation Patterns: references/common-patterns.md → Feature Flags Patterns

CRITICAL: Feature flag default values MUST be static (boolean, string, number) — they CANNOT reference local.* values.

# Correct: static default
feature "enable_monitoring" {
  default = false
}

# Incorrect: dynamic reference — FAILS
feature "enable_monitoring" {
  default = local.env.locals.enable_monitoring
}

Usage:

terragrunt apply --feature enable_monitoring=true
# or
export TG_FEATURE="enable_monitoring=true"

7. Generate Exclude Blocks (2025)

Fine-grained execution control (replaces deprecated skip).

Docs: Exclude Block Reference Patterns: references/common-patterns.md → Exclude Block Patterns

Actions: "plan", "apply", "destroy", "all", "all_except_output"

Production recommendation: Protect critical resources from accidental destruction:

exclude {
  if      = true
  actions = ["destroy"]
  exclude_dependencies = false
}

prevent_destroy = true

8. Generate Errors Blocks (2025)

Advanced error handling (replaces deprecated retryable_errors).

Docs: Errors Block Reference Patterns: references/common-patterns.md → Errors Block Patterns

9. Generate OpenTofu Engine Configuration (2025)

Use OpenTofu as the IaC engine.

Docs: Engine Documentation Patterns: references/common-patterns.md → OpenTofu Engine Patterns

10. Handling Custom Providers/Modules

When generating configs with custom providers:

  1. Identify the provider name, source, and version
  2. Search using WebSearch: "[provider] terraform provider [version] documentation"
  3. Or use Context7 MCP if available for structured docs
  4. Generate with proper required_providers block
  5. Document authentication requirements in comments

Generation Workflow

CRITICAL: Follow this workflow for EVERY generation task. Skipping steps leads to validation errors.

Step 1: Understand Requirements

  • What type of configuration? (root, child, standalone, stack)
  • Single or multi-environment?
  • What dependencies exist between modules?
  • What providers/modules will be used?

Step 2: Determine Architecture Pattern and Complete Checklist

Scenario Pattern Root.hcl Scope
Multi-env with shared root Pattern A Environment-agnostic
Single environment Pattern B Environment-aware
Centralized env vars Pattern C Environment-agnostic

MANDATORY: Before writing any files, complete and output this checklist to the user.

## Architecture Pattern Selection

[x] Identified architecture pattern: Pattern ___ (A/B/C)
[x] Root.hcl scope: [ ] environment-agnostic  OR  [ ] environment-aware
[x] env.hcl location: ___________________
[x] Child modules access env via: ___________________
[x] Verified: No file references a path that doesn't exist from its location

Step 3: Read Required Templates

Configuration Type Template to Read
Root configuration assets/templates/root/terragrunt.hcl
Child module assets/templates/child/terragrunt.hcl
Standalone module assets/templates/module/terragrunt.hcl
Stack file assets/templates/stack/terragrunt.stack.hcl
Catalog unit assets/templates/catalog/terragrunt.hcl

Also read: references/common-patterns.md — primary source for all generation patterns.

Step 4: Generate with Validation

Generation order for multi-environment projects:

  1. Generate root.hcl first

    • No read_terragrunt_config(find_in_parent_folders("env.hcl")) if environment-agnostic
    • remote_state block has encrypt = true
    • errors block used (not deprecated retryable_errors)
  2. Generate env.hcl files for each environment

    • locals block contains environment, aws_region, and module-specific vars
    • No references to files that don’t exist at that directory level
  3. Generate child modules — modules with NO dependencies first

    • include block uses find_in_parent_folders("root.hcl")
    • read_terragrunt_config(find_in_parent_folders("env.hcl")) present
    • terraform.source uses valid syntax (tfr:///, git::, or relative path)
  4. Generate dependent modules (RDS, EKS, etc.)

    • dependency blocks have mock_outputs
    • mock_outputs_allowed_terraform_commands includes ["validate", "plan", "destroy"]
    • Production modules have prevent_destroy = true and/or exclude block
  5. Run batch validation after ALL files are generated:

    terragrunt hcl fmt --check          # Format validation
    terragrunt dag graph                 # Dependency graph validation

    Invoke the terragrunt-validator skill for comprehensive validation.

Step 5: Fix and Re-Validate

If validation fails:

  1. Analyze errors (path resolution, missing variables, syntax errors)
  2. Fix issues in the specific file(s)
  3. Re-validate the fixed file(s)
  4. Repeat until ALL errors are resolved

Step 6: Present Results

Follow the Presentation Requirements section below.

Validation Workflow

Every generated configuration MUST be validated.

Incremental Validation

After generating root.hcl:

cd <infrastructure-directory>
terragrunt hcl fmt --check

After generating each child module:

cd <module-directory>
terragrunt hcl fmt --check
# If no dependencies on other modules:
terragrunt hcl validate --inputs

Full Validation

After all files are generated:

  1. Invoke the terragrunt-validator skill
  2. If validation fails: analyze errors, fix, and re-validate until all pass
  3. If validation succeeds: present configurations with usage instructions

Skip validation only for: Partial snippets, documentation examples, or explicit user request.

Presentation Requirements

After successful validation, present ALL of the following sections.

1. Directory Structure Summary

tree <infrastructure-directory>

2. Files Generated

| File | Purpose |
|------|---------|
| root.hcl | Shared configuration for all child modules (state backend, provider) |
| dev/env.hcl | Development environment variables |
| prod/env.hcl | Production environment variables |
| dev/vpc/terragrunt.hcl | VPC module for development |
| ... | ... |

3. Usage Instructions

## Usage Instructions

### Prerequisites
1. AWS credentials configured (`aws configure` or environment variables)
2. S3 bucket `<BUCKET_NAME>` exists for state storage
3. DynamoDB table `<TABLE_NAME>` exists for state locking

### Commands
cd <INFRASTRUCTURE_DIR>

terragrunt run --all init          # Initialize all modules
cd <ENV>/vpc && terragrunt plan    # Preview a specific module
terragrunt run --all plan          # Preview all changes
terragrunt run --all apply         # Apply changes (requires approval)
terragrunt run --all destroy       # Destroy (use with extreme caution)

4. Environment-Specific Notes

## Environment Notes

### Required Environment Variables
| Variable | Description | Example |
|----------|-------------|---------|
| AWS_PROFILE | AWS CLI profile to use | `my-profile` |
| AWS_REGION | AWS region (or set in provider) | `us-east-1` |

### Prerequisites
- [ ] S3 bucket `<BUCKET_NAME>` must exist before first run
- [ ] DynamoDB table `<TABLE_NAME>` must exist for state locking
- [ ] IAM permissions for Terraform state management

### Production-Specific Protections
| Module | Protection | Description |
|--------|------------|-------------|
| prod/rds | `prevent_destroy = true` | Prevents accidental database deletion |
| prod/rds | `exclude { actions = ["destroy"] }` | Blocks destroy commands |

5. Next Steps (Optional)

Suggest what the user might want to do next (add more modules, customize configurations, etc.)

Best Practices

Reference references/best_practices.md in the terragrunt-validator skill for comprehensive guidelines.

Key principles:

  • Use include blocks to inherit root configuration (DRY)
  • Always provide mock outputs for dependencies
  • Enable state encryption (encrypt = true)
  • Use generate blocks for provider configuration
  • Specify bounded version constraints (~> 5.0, not >= 5.0) for local/Git modules
  • Never hardcode credentials or secrets
  • Configure retry logic for transient errors

Note on Version Constraints with Registry Modules: When using Terraform Registry modules (e.g., tfr:///terraform-aws-modules/vpc/aws?version=5.1.0), they typically define their own required_providers. Omit generating required_providers in root.hcl to avoid conflicts — the module’s pinned version provides the constraint.

Anti-patterns to avoid:

  • Hardcoded account IDs, regions, or environment names
  • Missing mock outputs for dependencies
  • Duplicated configuration across modules
  • Unencrypted state storage
  • Missing or loose version constraints (except when using registry modules that define their own)
  • Root.hcl trying to read env.hcl that doesn’t exist at root level

Deprecated Attributes

Deprecated Replacement Reference
skip exclude block Docs
retryable_errors errors.retry block Docs
run-all run --all Migration
--terragrunt-* flags Unprefixed flags CLI Reference
TERRAGRUNT_* env vars TG_* env vars CLI Reference

Common Issues

For troubleshooting guidance, see references/troubleshooting.md, which covers:

  • Root.hcl cannot find env.hcl errors
  • Provider conflicts with registry modules
  • Feature flag validation errors
  • Child module env.hcl resolution issues

Anti-Patterns

NEVER use path_relative_to_include() as a module source path

  • WHY: This function returns a path relative to the INCLUDING file, not the included root file; using it as a module source creates path resolution failures that vary by directory depth.
  • BAD: path_relative_to_include() interpolated into the source string in a child module’s terraform.source.
  • GOOD: Use get_parent_terragrunt_dir() or construct explicit relative paths from the root terragrunt.hcl location.

NEVER duplicate backend configuration in every unit’s terragrunt.hcl

  • WHY: Copy-pasted remote_state {} blocks drift across units over time, creating inconsistent state key schemes and locking configurations that are difficult to audit.
  • BAD: A full remote_state { backend = "s3" ... } block repeated in every leaf module.
  • GOOD: Define remote state once in the root root.hcl and inherit it in every unit via include "root" { path = find_in_parent_folders("root.hcl") }.

NEVER ignore dependency output mismatches between plan and apply

  • WHY: When a dependency.outputs reference is evaluated and the dependency has not been applied, Terragrunt substitutes mock_outputs silently; if mock types differ from actual output types, the apply will fail with a type error.
  • BAD: Leave mock_outputs blocks with placeholder types and dismiss mock_outputs substitution warnings during terragrunt plan.
  • GOOD: Define mock_outputs whose types exactly match the actual dependency outputs, and use mock_outputs_allowed_terraform_commands = ["validate", "plan"] to limit substitution scope.

NEVER run terragrunt run --all apply without awareness of external dependency scope

  • WHY: By default, units outside the current directory tree are excluded from run --all operations, which can produce partial applies that leave infrastructure in an inconsistent state.
  • BAD: Run terragrunt run --all apply from a subdirectory expecting all transitive dependencies to be included automatically.
  • GOOD: Run from the repository root or pass --terragrunt-include-external-dependencies explicitly to ensure the full dependency graph is evaluated.

NEVER use the same terragrunt.hcl for both dev and prod environments without environment-level variable overrides

  • WHY: Sharing configuration without environment isolation causes prod deployments to silently inherit dev defaults (instance sizes, replica counts, retention policies).
  • BAD: A single terragrunt.hcl with no inputs block differentiation between environments.
  • GOOD: Use environment-level env.hcl files with inputs = { environment = "prod", instance_type = "m5.xlarge" } overrides that layer on top of shared defaults from the root configuration.

NEVER write a feature flag default that references local.*

  • WHY: Feature flag defaults must be static values, so a dynamic reference fails at evaluation time.
  • BAD: feature "enable_monitoring" { default = local.env.locals.enable_monitoring }
  • GOOD: feature "enable_monitoring" { default = false }, then override at run time with --feature enable_monitoring=true.

NEVER make an environment-agnostic root.hcl read env.hcl

  • WHY: env.hcl does not exist at the root level in Pattern A, so the path lookup fails and every child module breaks.
  • BAD: read_terragrunt_config(find_in_parent_folders("env.hcl")) inside root.hcl for a multi-environment layout.
  • GOOD: Read env.hcl from the child module and keep root.hcl to static values or get_env().

NEVER use deprecated skip, retryable_errors or run-all

  • WHY: These have replacements (exclude, errors.retry, run --all) and strict mode turns the deprecated forms into errors.
  • BAD: skip = true in a unit, or terragrunt run-all plan in usage instructions.
  • GOOD: Use an exclude block, an errors block with retry, and terragrunt run --all plan.

NEVER generate a remote_state block without encrypt = true

  • WHY: Unencrypted state can contain secrets in plain text, and the generation checklist requires encryption on every root.
  • BAD: remote_state config in root.hcl with no encrypt setting.
  • GOOD: Set encrypt = true in the remote_state config and generate the backend from the root template.

NEVER leave a production module without destroy protection

  • WHY: An accidental destroy on a database or similar resource is irreversible, and the generation checklist expects protection on production modules.
  • BAD: A prod/rds unit with no prevent_destroy and no exclude block.
  • GOOD: Set prevent_destroy = true and an exclude block with actions = ["destroy"] on production modules.

References

Templates — Read Before Generating

Configuration Type Template File When to Read
Root configuration assets/templates/root/terragrunt.hcl Before generating any root.hcl
Child module assets/templates/child/terragrunt.hcl Before generating any child module
Standalone module assets/templates/module/terragrunt.hcl Before generating standalone modules
Stack file assets/templates/stack/terragrunt.stack.hcl Before generating stacks
Catalog unit assets/templates/catalog/terragrunt.hcl Before generating catalog units

References

Reference Content When to Read
references/common-patterns.md All generation patterns with examples Always, before generating
references/troubleshooting.md Common issues and fixes When encountering errors
terragrunt-validator skill, references/best_practices.md Comprehensive best practices Always, before generating

Official Documentation