Terragrunt Validator
Comprehensive toolkit for validating, linting, testing, and automating Terragrunt configurations, HCL files, and Stacks. Use this skill when working with Terragrunt files (.hcl, terragrunt.hcl, terragrunt.stack.hcl), validating infrastructure-as-code, debugging Terragrunt configurations, performing dry-run testing with terragrunt plan, working with Terragrunt Stacks, or working with custom providers and modules.
npx skills add github:pantheon-org/tekhne --skill infrastructure--terragrunt--validatorPhilosophy
- Validate the whole dependency graph, not one unit: a unit that passes alone can still fail against its dependencies.
- Read the best practices reference before judging a configuration, so every finding is measured against a stated standard.
- Treat custom providers and modules as unknowns until their documentation, at the exact version in use, has been read.
- Prefer the Terragrunt 0.93+ CLI (
run --all,hcl fmt,dag graph) and strict mode so deprecated syntax is caught early. - Report each checklist item as pass or fail, because an unverified item is not a pass.
When to Use
- Validating
terragrunt.hcl,root.hcl,env.hclorterragrunt.stack.hclfiles before a plan or apply. - Debugging Terragrunt configuration errors such as dependency, include or
generateblock failures. - Running format, lint (tflint), security (Trivy or Checkov) and dry-run plan checks in one pass with
scripts/validate_terragrunt.sh. - Reviewing a configuration against the best practices checklists for includes, dependencies, security and DRY.
- Detecting custom providers and modules that need documentation lookup, using
scripts/detect_custom_resources.py.
When Not to Use
- Generating new Terragrunt configurations: use the
terragrunt-generatorskill instead. - Validating plain Terraform or OpenTofu projects with no Terragrunt files: use
terraform-validator. - Applying or destroying infrastructure: this skill validates and plans, it does not deploy.
- Judging runtime behaviour of deployed resources, which static validation and a dry-run plan cannot show.
Note: This skill is designed for Terragrunt 0.93+. For version compatibility details and command migration guidance, see
references/version_compatibility.md.
Core Capabilities
1. Comprehensive Validation Suite
Run the comprehensive validation script to perform all checks at once:
bash scripts/validate_terragrunt.sh [TARGET_DIR]
What it validates:
- HCL formatting (
terragrunt hcl fmt --check) - HCL input validation (
terragrunt hcl validate --inputs) - Terragrunt configuration syntax
- Terraform configuration validation
- Linting with tflint
- Security scanning with Trivy (or legacy tfsec)
- Dependency graph validation
- Dry-run planning
Environment variables:
SKIP_PLAN=true- Skip terragrunt plan stepSKIP_SECURITY=true- Skip security scanning (Trivy/tfsec)SKIP_LINT=true- Skip tflint lintingTG_STRICT_MODE=true- Enable strict mode (errors on deprecated features)
Example usage:
# Full validation
bash scripts/validate_terragrunt.sh ./infrastructure/prod
# Skip plan generation (faster)
SKIP_PLAN=true bash scripts/validate_terragrunt.sh ./infrastructure
# Only validate, skip linting and security
SKIP_LINT=true SKIP_SECURITY=true bash scripts/validate_terragrunt.sh
2. Custom Provider and Module Detection
Use the detection script to identify custom providers and modules that may require documentation lookup:
python3 scripts/detect_custom_resources.py [DIRECTORY] [--format text|json]
What it detects:
- Custom Terraform providers (non-HashiCorp)
- Remote modules (Git, Terraform Registry, HTTP)
- Provider versions
- Module versions and sources
Output formats:
text- Human-readable report with search recommendationsjson- Machine-readable format for automation
When custom resources are detected:
CRITICAL: You MUST look up documentation for EVERY detected custom resource (both providers AND modules). See the “Documentation Lookup” section in the Validation Workflow below for the complete mandatory process.
3. Step-by-Step Validation
For manual or granular validation, use these individual commands:
Format Validation
cd <target-directory>
terragrunt hcl fmt --check
# To auto-fix formatting
terragrunt hcl fmt
Configuration Validation
# Check HCL syntax and formatting
terragrunt hcl fmt --check
# Note: In Terragrunt 0.93+, for deeper configuration validation,
# initialize and validate (requires actual resources/credentials):
# terragrunt init && terragrunt validate
Terraform Validation
# Initialize if needed
terragrunt init
# Validate
terragrunt validate
Linting with tflint
# Initialize tflint (if .tflint.hcl exists)
tflint --init
# Run linting
tflint --recursive
Security Scanning with Trivy (Recommended)
Note: tfsec has been merged into Trivy and is no longer actively maintained. Use Trivy for all new projects.
# Using Trivy (recommended)
trivy config . --severity HIGH,CRITICAL
# With tfvars file
trivy config --tf-vars terraform.tfvars .
# Exclude downloaded modules
trivy config --tf-exclude-downloaded-modules .
# Legacy: Using tfsec (deprecated)
tfsec . --soft-fail
Alternative: Security Scanning with Checkov
# Scan directory
checkov -d . --framework terraform
# Scan with specific checks
checkov -d . --check CKV_AWS_21
# Output as JSON
checkov -d . --output json
Dependency Graph Validation
# Note: graph-dependencies command replaced with 'dag graph' in Terragrunt 0.93+
# Validate and display dependency graph
terragrunt dag graph
# Visualize dependencies (requires graphviz)
terragrunt dag graph | dot -Tpng > dependencies.png
Dry-Run Planning
# Single module
terragrunt plan
# All modules (new syntax - Terragrunt 0.93+)
terragrunt run --all plan
# Legacy syntax (deprecated)
# terragrunt run-all plan
4. Multi-Module Operations
For projects with multiple Terragrunt modules, use run --all (replaces deprecated run-all):
# Validate all modules
terragrunt run --all validate
# Plan all modules
terragrunt run --all plan
# Apply all modules
terragrunt run --all apply
# Destroy all modules
terragrunt run --all destroy
# Format all HCL files
terragrunt hcl fmt
# With parallelism
terragrunt run --all plan --parallelism 4
# With strict mode (errors on deprecated features)
terragrunt --strict-mode run --all plan
# Or via environment variable
TG_STRICT_MODE=true terragrunt run --all plan
5. HCL Input Validation (New in 0.93+)
Validate that all required inputs are set and no unused inputs exist:
# Validate inputs
terragrunt hcl validate --inputs
# Show paths of invalid files
terragrunt hcl validate --show-config-path
# Combine with run --all to exclude invalid files
terragrunt run --all plan --queue-excludes-file <(terragrunt hcl validate --show-config-path || true)
6. Strict Mode
Enable strict mode to catch deprecated features early:
# Via CLI flag
terragrunt --strict-mode run --all plan
# Via environment variable (recommended for CI/CD)
export TG_STRICT_MODE=true
terragrunt run --all plan
# Check available strict controls
terragrunt info strict
Specific Strict Controls:
For finer-grained control, use --strict-control to enable specific controls:
# Enable specific strict controls
terragrunt run --all plan --strict-control cli-redesign --strict-control deprecated-commands
# Via environment variable (comma-separated)
TG_STRICT_CONTROL='cli-redesign,deprecated-commands' terragrunt run --all plan
# Available strict controls:
# - cli-redesign: Errors on deprecated CLI syntax
# - deprecated-commands: Errors on deprecated commands (run-all, hclfmt, etc.)
# - root-terragrunt-hcl: Errors when using root terragrunt.hcl (use root.hcl instead)
# - skip-dependencies-inputs: Improves performance by not reading dependency inputs
# - bare-include: Errors on bare include blocks (use named includes)
7-11. CLI Commands and Features
Terragrunt 0.93+ includes many new CLI commands and features. For detailed documentation on:
- Render configuration and info print
- Find and list units
- Run summary and reports
- Terragrunt Stacks (GA in v0.78.0+)
- Exec command for running arbitrary programs
- Feature flags for safe infrastructure changes
- Experiments for unstable features
See references/cli_commands.md for complete usage and examples.
Validation Workflow
Follow this workflow when validating Terragrunt configurations:
Step 0: Read Best Practices Reference (MANDATORY FIRST STEP)
You MUST read the best practices reference file BEFORE starting validation. This is not optional.
# Read the best practices reference file first
cat references/best_practices.md
This ensures you understand the patterns, anti-patterns, and checklists you will verify.
Initial Assessment
-
Understand the structure:
tree -L 3 <infrastructure-directory> -
Identify Terragrunt files:
find . -name "*.hcl" -o -name "terragrunt.hcl" -
Detect custom resources:
python3 scripts/detect_custom_resources.py .
Documentation Lookup (MANDATORY for ALL detected custom resources)
CRITICAL: If ANY custom providers or modules are detected, you MUST look up documentation for EACH ONE. Do not skip any.
For custom providers:
- Option A - Context7 MCP (Preferred): Use Context7 for structured documentation lookup
- Step 1: Resolve library ID:
mcp__context7__resolve-library-idwith provider name (e.g., “datadog terraform provider”) - Step 2: REQUIRED - Fetch documentation:
mcp__context7__get-library-docswith the resolved library ID - Use
topic: "authentication"ortopic: "configuration"for targeted docs
- Step 1: Resolve library ID:
- Option B - WebSearch: Search for provider documentation
- Query format:
"{provider_source} terraform provider documentation version {version}" - Example:
"mongodb/mongodbatlas terraform provider documentation version 1.14.0"
- Query format:
For custom modules (EQUALLY IMPORTANT - DO NOT SKIP):
- Terraform Registry modules:
- Use Context7:
mcp__context7__resolve-library-idwith module name (e.g., “terraform-aws-modules vpc”) - Then fetch docs with
mcp__context7__get-library-docs - Or visit
https://registry.terraform.io/modules/{source}/{version}
- Use Context7:
- Git modules: Use WebSearch with the repository URL to find README or documentation
- HTTP modules: Investigate the source URL for documentation
- Pay attention to version compatibility with your Terraform/Terragrunt version
Documentation lookup workflow:
a) Run detect_custom_resources.py
b) For EACH custom provider/module:
- Note the exact version
- Use Context7 MCP:
1. mcp__context7__resolve-library-id with libraryName: "{provider/module name}"
2. mcp__context7__get-library-docs with:
- context7CompatibleLibraryID: "{resolved ID}"
- topic: "authentication" (for auth requirements)
- topic: "configuration" (for setup requirements)
- OR use WebSearch with version-specific queries
- Review documentation for:
* Required configuration blocks
* Authentication requirements (API keys, credentials)
* Available resources/data sources
* Known issues or breaking changes in the version
c) Apply learnings to validation/troubleshooting
d) Document findings if issues are encountered
Example using Context7 MCP:
# 1. Detect custom resources
python3 scripts/detect_custom_resources.py ./infrastructure
# Output: Provider: datadog/datadog, Version: 3.30.0
# 2. Resolve library ID
mcp__context7__resolve-library-id with libraryName: "datadog terraform provider"
# Result: /datadog/terraform-provider-datadog
# 3. Fetch authentication docs (REQUIRED)
mcp__context7__get-library-docs with:
context7CompatibleLibraryID: "/datadog/terraform-provider-datadog"
topic: "authentication"
# 4. Fetch configuration docs
mcp__context7__get-library-docs with:
context7CompatibleLibraryID: "/datadog/terraform-provider-datadog"
topic: "configuration"
Validation Execution
-
Run comprehensive validation:
bash scripts/validate_terragrunt.sh <target-directory> -
Review output for errors:
- Format errors → Fix with
terragrunt hcl fmt - Configuration errors → Check terragrunt.hcl syntax and inputs
- Terraform validation errors → Check .tf files or generated configs
- Linting issues → Review tflint output and fix
- Security issues → Review tfsec output and address
- Dependency errors → Check dependency blocks and paths
- Plan errors → Review Terraform configuration and provider setup
- Format errors → Fix with
Best Practices Check (REQUIRED - Must Complete All Checklists)
You MUST verify each checklist item below and document the result (✅ pass or ❌ fail). Incomplete verification is not acceptable.
Perform explicit best practices verification using references/best_practices.md:
Configuration Pattern Checklist - verify each item:
[ ] Include blocks: Child modules use `include "root" { path = find_in_parent_folders("root.hcl") }`
[ ] Named includes: All include blocks have names (not bare `include {}`)
[ ] Root file naming: Root config is named `root.hcl` (not `terragrunt.hcl`)
[ ] Environment configs: Environment-level configs named `env.hcl` (not `terragrunt.hcl`)
[ ] Common variables: Shared variables in `common.hcl` read via `read_terragrunt_config()`
Dependency Management Checklist:
[ ] Mock outputs: ALL dependency blocks have mock_outputs for validation
[ ] Mock allowed commands: mock_outputs_allowed_terraform_commands includes ["validate", "plan", "init"]
[ ] Explicit paths: Dependency config_path uses relative paths ("../vpc" not absolute)
[ ] No circular deps: Run `terragrunt dag graph` to verify no cycles
Security Checklist:
[ ] State encryption: remote_state config has `encrypt = true`
[ ] State locking: DynamoDB table configured for S3 backend
[ ] No hardcoded credentials: Search for patterns like "AKIA", "password =", account IDs
[ ] Sensitive variables: Passwords/keys use `sensitive = true` in variable blocks
[ ] IAM roles: Provider uses assume_role instead of static credentials
DRY Principle Checklist:
[ ] Generate blocks: Provider and backend configs use `generate` blocks
[ ] Version constraints: terragrunt_version_constraint and terraform_version_constraint set
[ ] Reusable locals: Common values in shared files, not duplicated
[ ] if_exists: Generate blocks use appropriate if_exists strategy
Quick grep checks to run:
# Check for hardcoded AWS account IDs
grep -r "[0-9]\{12\}" --include="*.hcl" . | grep -v mock
# Check for potential credentials
grep -ri "password\s*=" --include="*.hcl" .
grep -ri "api_key\s*=" --include="*.hcl" .
# Check for dependencies without mock_outputs
grep -l "dependency\s" --include="*.hcl" -r . | xargs grep -L "mock_outputs"
# Check for terragrunt.hcl files in non-module directories (anti-pattern)
find . -name "terragrunt.hcl" -not -path "*/.terragrunt-cache/*" | head -20
Troubleshooting
Common issues and resolutions:
Issue: Module not found
rm -rf .terragrunt-cache
terragrunt init
Issue: Provider authentication errors
- Check provider configuration in generated files
- Verify environment variables or credentials
- Review provider documentation from WebSearch
Issue: Dependency errors
- Check dependency paths are correct
- Ensure mock_outputs are provided for validation
- Review dependency graph with
terragrunt dag graph
Issue: State locking errors
terragrunt force-unlock <LOCK_ID>
Issue: Unknown provider or module parameters
- Re-run custom resource detection
- Use WebSearch to look up current documentation
- Check version compatibility
Issue: Generate block conflicts (file already exists)
ERROR: The file path ./versions.tf already exists and was not generated by terragrunt.
Can not generate terraform file: ./versions.tf already exists
Solution: This occurs when static .tf files exist that conflict with Terragrunt’s generate blocks. Either:
- Remove the conflicting static files (
versions.tf,provider.tf,backend.tf) - Or use
if_exists = "skip"in the generate block to not overwrite existing files
# Remove conflicting files
rm -f versions.tf provider.tf backend.tf
rm -rf .terragrunt-cache
Issue: Root terragrunt.hcl anti-pattern warning
WARN: Using `terragrunt.hcl` as the root of Terragrunt configurations is an anti-pattern
Solution: In Terragrunt 0.93+, the root configuration file should be named root.hcl instead of terragrunt.hcl. Rename the file:
mv terragrunt.hcl root.hcl
# Update include blocks in child modules to reference root.hcl
Best Practices Integration
Reference the comprehensive best practices guide for detailed recommendations:
# Read the best practices reference
cat references/best_practices.md
Key best practices to check:
- ✅ Use
includefor shared configuration - ✅ Provide mock_outputs for dependencies
- ✅ Use
generateblocks for provider config - ✅ Enable state encryption and locking
- ✅ Use environment variables for dynamic values
- ✅ Specify version constraints
- ✅ Avoid hardcoded values
- ✅ Use meaningful directory structure
- ✅ Enable security features (encryption, IAM roles)
When validating, check for anti-patterns:
- ❌ Hardcoded credentials or account IDs
- ❌ Missing mock outputs
- ❌ Overly deep directory nesting
- ❌ Duplicated configuration across modules
- ❌ Missing version constraints
- ❌ Unencrypted state
Refer to references/best_practices.md for complete examples and detailed guidance.
Tool Requirements
Required:
- terragrunt (>= 0.93.0 recommended for new CLI)
- terraform or opentofu (>= 1.6.0 recommended)
Optional but recommended:
- tflint - HCL linting
- trivy - Security scanning (replaces tfsec)
- checkov - Alternative security scanner (750+ built-in policies)
- graphviz (dot) - Dependency visualization
- jq - JSON parsing
- python3 - For custom resource detection script
Deprecated tools:
- tfsec - Merged into Trivy, no longer actively maintained
Installation commands:
# macOS
brew install terragrunt terraform tflint trivy graphviz jq
# Install Trivy (recommended security scanner)
brew install trivy
# Install Checkov (alternative security scanner)
pip3 install checkov
# Legacy tfsec (deprecated - use trivy instead)
# brew install tfsec
# Linux - Trivy
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
# Linux - Checkov
pip3 install checkov
# Verify installations
terragrunt --version
trivy --version
checkov --version
Automated Workflows
CI/CD Integration
Example validation in CI/CD pipeline:
#!/bin/bash
# ci-validate.sh
set -e
echo "Installing dependencies..."
# Install terragrunt, terraform, tflint, tfsec
echo "Detecting custom resources..."
python3 scripts/detect_custom_resources.py . --format json > custom_resources.json
# Could integrate with automated documentation lookup here
echo "Running validation suite..."
SKIP_PLAN=true bash scripts/validate_terragrunt.sh .
echo "Validation complete!"
Pre-commit Hook
Example pre-commit hook for local development:
#!/bin/bash
# .git/hooks/pre-commit
# Format check
terragrunt hcl fmt --check || {
echo "HCL formatting issues found. Run: terragrunt hcl fmt"
exit 1
}
# Quick HCL syntax validation (Terragrunt 0.93+)
# Note: For full validation, use: terragrunt init && terragrunt validate
# But that requires credentials. HCL format check catches syntax errors.
echo "Pre-commit validation passed!"
Troubleshooting
For detailed troubleshooting guidance including debug mode, common error patterns, and resolution steps, see references/troubleshooting.md.
Output Interpretation
For detailed guidance on interpreting validation output, success/warning/error indicators, see references/output_interpretation.md.
Advanced Usage
For advanced topics including custom validation rules, security policies, and dependency graph analysis, see references/advanced_usage.md.
Anti-Patterns
NEVER validate only the current unit in isolation
- WHY: Terragrunt dependency chains mean a unit may validate cleanly on its own but fail when its dependencies have incompatible outputs or missing inputs; isolated validation misses integration errors.
- BAD: Run
terragrunt validatein a single leaf unit directory and treat a clean result as full validation. - GOOD: Use
terragrunt run --all validatefrom the relevant parent directory to validate all units in the dependency graph together.
NEVER skip --terragrunt-log-level debug when diagnosing HCL function errors
- WHY: HCL function evaluation errors produce cryptic messages at default log levels; debug mode shows the evaluated function calls and helps pinpoint the exact source of the failure.
- BAD: Attempt to debug
path_relative_to_include()orfind_in_parent_folders()errors using the default log level output. - GOOD: Add
--terragrunt-log-level debug(or setTG_LOG=debug) to get the full HCL evaluation trace and locate the offending expression.
NEVER accept terragrunt validate passing as equivalent to terraform validate
- WHY: Terragrunt’s validate wraps Terraform validate but may not catch all HCL parsing errors in
terragrunt.hclincludes; the two tools validate different layers of the configuration stack. - BAD: Skip
terraform validateand rely solely onterragrunt validateas the complete validation signal. - GOOD: Run
terragrunt validatefor Terragrunt-level HCL and input checks, then runterraform validate(orterragrunt init && terragrunt validate) for Terraform provider schema validation.
NEVER ignore dependency mock output warnings in plan output
- WHY: Mock output substitution warnings signal that the plan result is based on placeholder values; if mock types differ from actual outputs, the plan is unreliable and the apply will fail.
- BAD: Dismiss
mock_outputssubstitution warnings interragrunt planoutput as expected and harmless. - GOOD: Verify that mock output types exactly match actual dependency outputs after the dependency has been applied at least once; update mocks if types change.
NEVER start validating before reading the best practices reference
- WHY: The checklists in
references/best_practices.mddefine what a pass looks like; validating without them produces findings measured against memory rather than the stated standard. - BAD: Run
bash scripts/validate_terragrunt.shand report on its output alone. - GOOD: Read
references/best_practices.mdfirst, then run the script and record each checklist item as pass or fail.
NEVER skip documentation lookup for a detected custom provider or module
- WHY: Custom providers and modules have their own required configuration, authentication and version-specific breaking changes, so errors cannot be diagnosed without them.
- BAD: Run the detection script, see a non-HashiCorp provider listed, and move straight on to validation.
- GOOD: Look up documentation for every detected provider and module at its exact version, then apply what it says to the validation.
NEVER leave a dependency block without mock_outputs
- WHY: Without mocks,
validateandplanfail for any dependency that has not been applied, so the configuration cannot be checked in CI. - BAD: a
dependency "vpc"block that sets onlyconfig_path. - GOOD: Add
mock_outputsand setmock_outputs_allowed_terraform_commands = ["validate", "plan", "init"]on every dependency block.
NEVER hardcode credentials or account IDs in HCL files
- WHY: HCL files are committed to version control, so a hardcoded key or account ID leaks to everyone with repository access.
- BAD:
password = "hunter2"or a literal 12-digit AWS account ID ininputs. - GOOD: Read values from environment variables or
assume_role, and run the grep checks forpassword =,api_key =and 12-digit IDs before signing off.
NEVER use the deprecated run-all command or a root terragrunt.hcl in 0.93+ configurations
- WHY:
run-alland a root file namedterragrunt.hclare deprecated; strict mode turns them into errors, and the root name triggers an anti-pattern warning. - BAD:
terragrunt run-all planwithinclude { path = find_in_parent_folders() }pointing atterragrunt.hcl. - GOOD:
terragrunt run --all plan, with the root renamed toroot.hcland a namedinclude "root"block.
NEVER leave static .tf files beside a generate block that writes the same file
- WHY: Terragrunt refuses to overwrite a file it did not generate, so the run fails with “already exists and was not generated by terragrunt”.
- BAD: A hand-written
versions.tf,provider.tforbackend.tfin a directory whosegenerateblock produces the same name. - GOOD: Remove the static files and clear
.terragrunt-cache, or setif_exists = "skip"in the generate block.
References
Scripts
scripts/validate_terragrunt.sh- Comprehensive validation suitescripts/detect_custom_resources.py- Custom provider/module detector
References
references/best_practices.md- Comprehensive best practices guidereferences/version_compatibility.md- Terragrunt version compatibility and CLI migrationreferences/cli_commands.md- Detailed CLI commands, Stacks, feature flags, experimentsreferences/troubleshooting.md- Debug mode and common error patternsreferences/output_interpretation.md- Validation output indicatorsreferences/advanced_usage.md- Custom rules, security policies, dependency analysis
External Documentation
Explicit NEVER/WHY/consequence triplets with side-by-side bad/good examples. Generic warnings score low — concrete failure modes with real impact (security, correctness, performance) score high.
Runtime validation via eval scenarios proving the skill actually changes agent behaviour. Requires evals/ directory, instructions.json, ≥3 scenarios, and ≥80% instruction coverage in summary.json.
Appropriate rigidity for the skill type: strict rules for critical/security skills, balanced steps for process skills, flexible options for tool skills. Mismatched calibration (too rigid or too loose) reduces score.
Expert-only knowledge the AI couldn't derive on its own — domain-specific patterns, production gotchas, decision frameworks, and non-obvious tool behaviour. High scores mean <5% redundancy with what the model already knows.
A clear philosophy plus numbered step-by-step workflows with explicit entry/exit points. Should answer: what is the core principle, how to execute it, and when (and when not) to apply it.
Rich activation keywords and trigger scenarios in the description so the skill fires when needed. Best descriptions read like an exhaustive list of the tasks and concepts that should invoke this skill.
Copy-paste-ready code, complete runnable examples, real file paths and commands. Pseudocode and abstract descriptions score low — concrete, executable content scores high.
SKILL.md as a <100-line navigation hub pointing to focused references/ files. Content should be lazy-loaded on demand, not front-loaded. Includes actionable 'When to Use' conditions per reference.
Proper frontmatter, single-task focus, activation keywords, and cross-harness portability. Skills must work across 40+ agentic harnesses without hardcoded paths or agent-specific references.
Expert-only knowledge the AI couldn't derive on its own — domain-specific patterns, production gotchas, decision frameworks, and non-obvious tool behaviour. High scores mean <5% redundancy with what the model already knows.
A clear philosophy plus numbered step-by-step workflows with explicit entry/exit points. Should answer: what is the core principle, how to execute it, and when (and when not) to apply it.
Explicit NEVER/WHY/consequence triplets with side-by-side bad/good examples. Generic warnings score low — concrete failure modes with real impact (security, correctness, performance) score high.
Proper frontmatter, single-task focus, activation keywords, and cross-harness portability. Skills must work across 40+ agentic harnesses without hardcoded paths or agent-specific references.
SKILL.md as a <100-line navigation hub pointing to focused references/ files. Content should be lazy-loaded on demand, not front-loaded. Includes actionable 'When to Use' conditions per reference.
Appropriate rigidity for the skill type: strict rules for critical/security skills, balanced steps for process skills, flexible options for tool skills. Mismatched calibration (too rigid or too loose) reduces score.
Rich activation keywords and trigger scenarios in the description so the skill fires when needed. Best descriptions read like an exhaustive list of the tasks and concepts that should invoke this skill.
Copy-paste-ready code, complete runnable examples, real file paths and commands. Pseudocode and abstract descriptions score low — concrete, executable content scores high.
No eval scenarios available.
Advanced Usage
Custom Validation Rules
Create custom tflint rules by adding .tflint.hcl:
plugin "terraform" {
enabled = true
preset = "recommended"
}
plugin "aws" {
enabled = true
version = "0.27.0"
source = "github.com/terraform-linters/tflint-ruleset-aws"
}
rule "terraform_naming_convention" {
enabled = true
}
Custom Security Policies
Create custom tfsec policies by adding .tfsec/config.yml:
minimum_severity: MEDIUM
exclude:
- AWS001 # Example: exclude specific rules
Dependency Graph Analysis
Analyze complex dependency chains:
# Generate detailed graph (Terragrunt 0.93+ syntax)
terragrunt dag graph > graph.dot
# Convert to visual format
dot -Tpng graph.dot > graph.png
dot -Tsvg graph.dot > graph.svg
# Analyze for circular dependencies
grep -A5 "cycle" <(terragrunt dag graph 2>&1)
Terragrunt Best Practices and Common Patterns
Overview
This reference document provides best practices, common patterns, and anti-patterns for Terragrunt configurations. Use this as a guide when validating or creating Terragrunt code.
Directory Structure
Recommended Structure
infrastructure/
├── terragrunt.hcl # Root Terragrunt config
├── common.hcl # Shared configuration
├── prod/
│ ├── terragrunt.hcl # Environment-level config
│ ├── vpc/
│ │ └── terragrunt.hcl # Module-specific config
│ ├── database/
│ │ └── terragrunt.hcl
│ └── app/
│ └── terragrunt.hcl
├── staging/
│ └── ... (similar structure)
└── dev/
└── ... (similar structure)
Anti-Pattern: Flat Structure
❌ Avoid flat structures without environment separation:
infrastructure/
├── vpc.hcl
├── database.hcl
├── app.hcl
DRY Principles
Use include for Shared Configuration
✅ Good Practice:
# Root terragrunt.hcl
remote_state {
backend = "s3"
config = {
bucket = "my-terraform-state"
key = "${path_relative_to_include()}/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
# Child terragrunt.hcl
include "root" {
path = find_in_parent_folders()
}
Use read_terragrunt_config for Shared Variables
✅ Good Practice:
# common.hcl
locals {
region = "us-east-1"
environment = "prod"
tags = {
Terraform = "true"
Environment = local.environment
}
}
# terragrunt.hcl
locals {
common = read_terragrunt_config(find_in_parent_folders("common.hcl"))
}
inputs = {
region = local.common.locals.region
tags = local.common.locals.tags
}
Dependencies
Explicit Dependencies
✅ Good Practice:
dependency "vpc" {
config_path = "../vpc"
}
dependency "database" {
config_path = "../database"
# Mock outputs for validation
mock_outputs = {
endpoint = "mock-db-endpoint"
port = 5432
}
# Allow mock outputs during plan
mock_outputs_allowed_terraform_commands = ["validate", "plan"]
}
inputs = {
vpc_id = dependency.vpc.outputs.vpc_id
database_endpoint = dependency.database.outputs.endpoint
}
Anti-Pattern: Implicit Dependencies via Remote State
❌ Avoid accessing remote state directly:
# This makes dependencies unclear
inputs = {
vpc_id = data.terraform_remote_state.vpc.outputs.vpc_id
}
Mock Outputs for Testing
Provide Mock Outputs
✅ Good Practice:
dependency "network" {
config_path = "../network"
mock_outputs = {
vpc_id = "vpc-mock123"
subnet_ids = ["subnet-mock1", "subnet-mock2"]
}
mock_outputs_allowed_terraform_commands = ["validate", "plan", "init"]
mock_outputs_merge_strategy_with_state = "shallow"
}
This allows running terragrunt plan without deploying dependencies first.
Generate Blocks
Use generate for Provider Configuration
✅ Good Practice:
generate "provider" {
path = "provider.tf"
if_exists = "overwrite_terragrunt"
contents = <<EOF
provider "aws" {
region = "${local.region}"
assume_role {
role_arn = "arn:aws:iam::${local.account_id}:role/TerraformRole"
}
default_tags {
tags = ${jsonencode(local.tags)}
}
}
EOF
}
Use generate for Backend Configuration
✅ Good Practice:
generate "backend" {
path = "backend.tf"
if_exists = "overwrite_terragrunt"
contents = <<EOF
terraform {
backend "s3" {}
}
EOF
}
Input Variables
Use inputs Block
✅ Good Practice:
inputs = {
environment = local.environment
region = local.region
# Use dependency outputs
vpc_id = dependency.vpc.outputs.vpc_id
# Use functions
instance_count = get_env("INSTANCE_COUNT", 3)
# Merge tags
tags = merge(
local.common_tags,
{
Module = "app"
}
)
}
Anti-Pattern: Duplicating Inputs
❌ Avoid repeating the same inputs:
# Don't do this across multiple modules
inputs = {
region = "us-east-1" # Repeated everywhere
tags = { # Repeated everywhere
Terraform = "true"
}
}
terraform Block
Specify Terraform and Provider Versions
✅ Good Practice:
terraform {
source = "tfr:///terraform-aws-modules/vpc/aws?version=5.1.0"
}
generate "versions" {
path = "versions.tf"
if_exists = "overwrite_terragrunt"
contents = <<EOF
terraform {
required_version = ">= 1.6.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
EOF
}
Error Handling
Use get_env with Defaults
✅ Good Practice:
locals {
account_id = get_env("AWS_ACCOUNT_ID", "")
}
# Validate required environment variables
inputs = {
account_id = local.account_id != "" ? local.account_id : run_cmd("--terragrunt-quiet", "aws", "sts", "get-caller-identity", "--query", "Account", "--output", "text")
}
Use try for Optional Values
✅ Good Practice:
locals {
env_config = read_terragrunt_config(find_in_parent_folders("env.hcl", "empty.hcl"))
# Safely access potentially missing values
instance_type = try(local.env_config.locals.instance_type, "t3.micro")
}
Common Anti-Patterns
1. Hardcoding Values
❌ Bad:
inputs = {
region = "us-east-1" # Hardcoded
account_id = "123456789012" # Hardcoded
}
✅ Good:
locals {
region = get_env("AWS_REGION", "us-east-1")
account_id = get_aws_account_id()
}
inputs = {
region = local.region
account_id = local.account_id
}
2. Not Using Mock Outputs
❌ Bad:
dependency "vpc" {
config_path = "../vpc"
# No mock outputs - can't validate without deploying vpc
}
3. Deep Nesting
❌ Bad:
infrastructure/
└── prod/
└── us-east-1/
└── vpc/
└── public/
└── subnet-1/
└── terragrunt.hcl
✅ Good:
infrastructure/
└── prod/
└── vpc/
└── terragrunt.hcl # Configure all subnets here
4. Not Using Functions
❌ Bad:
# Manually maintaining paths
remote_state {
config = {
key = "prod/vpc/terraform.tfstate"
}
}
✅ Good:
remote_state {
config = {
key = "${path_relative_to_include()}/terraform.tfstate"
}
}
Security Best Practices
1. Enable State Encryption
remote_state {
backend = "s3"
config = {
encrypt = true
kms_key_id = "arn:aws:kms:us-east-1:123456789012:key/..."
}
}
2. Use IAM Roles for Authentication
generate "provider" {
path = "provider.tf"
if_exists = "overwrite_terragrunt"
contents = <<EOF
provider "aws" {
assume_role {
role_arn = "arn:aws:iam::${local.account_id}:role/TerraformRole"
}
}
EOF
}
3. Enable State Locking
remote_state {
backend = "s3"
config = {
dynamodb_table = "terraform-locks"
}
}
4. Use Sensitive Variables
inputs = {
# Mark sensitive inputs
database_password = get_env("DB_PASSWORD") # Never hardcode
}
Testing and Validation
1. Modern Terragrunt CLI (v0.93+)
Note: Terragrunt 0.93+ uses a redesigned CLI with significant changes:
run-allis deprecated → userun --allhclfmtis deprecated → usehcl fmtvalidate-inputsis deprecated → usehcl validate --inputsgraph-dependenciesis deprecated → usedag graph- The
--terragrunt-non-interactiveflag is no longer needed or supported
2. Validate Before Apply
# Format check (new syntax)
terragrunt hcl fmt --check
# Input validation (new in 0.93+)
terragrunt hcl validate --inputs
# Initialize (required for validation)
terragrunt init
# Validate Terraform configuration
terragrunt validate
# Generate plan
terragrunt plan
3. Use run --all for Multi-Module Operations
Note:
run-allis deprecated. Userun --allinstead.
# Validate all modules
terragrunt run --all validate
# Plan all modules
terragrunt run --all plan
# Apply all modules
terragrunt run --all apply
# With strict mode (errors on deprecated features)
terragrunt --strict-mode run --all plan
# Or via environment variable
TG_STRICT_MODE=true terragrunt run --all plan
Performance Optimization
1. Use Shallow Dependencies
dependency "vpc" {
config_path = "../vpc"
# Only fetch specific outputs
mock_outputs_merge_strategy_with_state = "shallow"
}
2. Parallelize Operations
# Run operations in parallel (new syntax)
terragrunt run --all apply --parallelism 4
# Legacy syntax (deprecated)
# terragrunt run-all apply --terragrunt-parallelism=4
3. Use Caching
# Cache downloaded modules
terraform {
source = "tfr:///terraform-aws-modules/vpc/aws?version=5.1.0"
}
Troubleshooting Common Issues
Issue: Circular Dependencies
Symptom: “Cycle detected in dependency graph”
Solution:
- Review dependency chain
- Separate tightly coupled resources into single module
- Use data sources instead of dependencies where appropriate
Issue: State Locking Errors
Symptom: “Error acquiring the state lock”
Solution:
# Force unlock (use with caution)
terragrunt force-unlock <LOCK_ID>
Issue: Module Not Found
Symptom: “Module not found”
Solution:
# Clear cache and reinitialize
rm -rf .terragrunt-cache
terragrunt init
Version Compatibility
Terragrunt Version Constraints
Specify minimum Terragrunt version:
# For new CLI features (recommended)
terragrunt_version_constraint = ">= 0.93.0"
# For backwards compatibility with older features
# terragrunt_version_constraint = ">= 0.48.0"
Terraform Version Constraints
terraform_version_constraint = ">= 1.6.0, < 2.0.0"
References
CLI Commands Reference (Terragrunt 0.93+)
Render Configuration
# Render configuration to JSON
terragrunt render --json
# Render and write to file
terragrunt render --json --write
# Output goes to terragrunt.rendered.json
Info Print (replaces terragrunt-info)
# Get contextual information about current configuration
terragrunt info print
# Output includes:
# - config_path
# - download_dir
# - terraform_binary
# - working_dir
Find and List Units
# Find all units/stacks in directory
terragrunt find
# Output as JSON
terragrunt find --json
# Include dependency information
terragrunt find --json --dag
# List units (simpler output)
terragrunt list
Run Summary and Reports
# Run with summary output (default in newer versions)
terragrunt run --all plan
# Disable summary output
terragrunt run --all plan --summary-disable
# Generate detailed report file
terragrunt run --all plan --report-file=report.json
# CSV format report
terragrunt run --all plan --report-file=report.csv
Terragrunt Stacks (GA in v0.78.0+)
Terragrunt Stacks provide declarative infrastructure generation using terragrunt.stack.hcl files.
Stack File Structure
# terragrunt.stack.hcl
locals {
environment = "dev"
aws_region = "us-east-1"
}
# Define a unit (generates a single terragrunt.hcl)
unit "vpc" {
source = "git::git@github.com:acme/infra-catalog.git//units/vpc?ref=v0.0.1"
path = "vpc"
values = {
environment = local.environment
cidr = "10.0.0.0/16"
}
}
unit "database" {
source = "git::git@github.com:acme/infra-catalog.git//units/database?ref=v0.0.1"
path = "database"
values = {
environment = local.environment
vpc_path = "../vpc"
}
}
# Include reusable stacks
stack "monitoring" {
source = "git::git@github.com:acme/infra-catalog.git//stacks/monitoring?ref=v0.0.1"
path = "monitoring"
values = {
environment = local.environment
}
}
Stack Commands
# Generate stack (creates .terragrunt-stack directory)
terragrunt stack generate
# Generate stack without validation
terragrunt stack generate --no-stack-validate
# Run command on all stack units
terragrunt stack run plan
terragrunt stack run apply
# Clean generated stack directories
terragrunt stack clean
# Get stack outputs
terragrunt stack output
Stack Validation Control
Use no_validation attribute to skip validation for specific units:
unit "experimental" {
source = "git::git@github.com:acme/infra-catalog.git//units/experimental?ref=v0.0.1"
path = "experimental"
# Skip validation for this unit (useful for incomplete/experimental units)
no_validation = true
values = {
environment = local.environment
}
}
Benefits of Stacks
- Clean working directory: Generated code in hidden
.terragrunt-stackdirectory - Reusable patterns: Define infrastructure patterns once, deploy many times
- Version pinning: Different environments can pin different versions
- Atomic updates: Easy rollbacks of both modules and configurations
Exec Command (Run Arbitrary Programs)
The exec command allows you to run arbitrary programs against units with Terragrunt context. This is useful for integrating other tools like tflint, checkov, or AWS CLI with Terragrunt’s configuration.
# Run tflint with unit context (TF_VAR_ env vars available)
terragrunt exec -- tflint
# Run checkov against specific unit
terragrunt exec -- checkov -d .
# Run AWS CLI with unit's configuration
terragrunt exec -- aws s3 ls s3://my-bucket
# Run custom scripts with Terragrunt context
terragrunt exec -- ./scripts/validate.sh
# Run across all units
terragrunt run --all exec -- tflint
Key Features:
- Terragrunt loads the inputs for the unit and makes them available as
TF_VAR_prefixed environment variables - Works with any program that can use environment variables
- Integrates with Terragrunt’s authentication context (e.g., AWS profiles)
- Can be combined with
run --allfor multi-unit operations
Use Cases:
- Running security scanners (checkov, trivy) with unit context
- Executing linters (tflint) per unit
- Running operational commands (AWS CLI) with correct credentials
- Custom validation scripts that need Terragrunt inputs
Feature Flags (Production Feature)
Terragrunt supports first-class Feature Flags for safe infrastructure changes. Feature flags allow you to integrate incomplete work without risk, decouple release from deployment, and codify IaC evolution.
Defining Feature Flags
# terragrunt.hcl
feature "enable_monitoring" {
default = false
}
feature "use_new_vpc" {
default = true
}
inputs = {
monitoring_enabled = feature.enable_monitoring.value
vpc_version = feature.use_new_vpc.value ? "v2" : "v1"
}
Using Feature Flags via CLI
# Enable a feature flag
terragrunt plan --feature enable_monitoring=true
# Enable multiple feature flags
terragrunt plan --feature enable_monitoring=true --feature use_new_vpc=false
# Via environment variable
TG_FEATURE='enable_monitoring=true' terragrunt plan
Feature Flags with run –all
# Apply feature flag across all units
terragrunt run --all plan --feature enable_monitoring=true
Benefits:
- Safe rollouts: Test changes on subset of infrastructure
- Gradual migrations: Enable new features incrementally
- A/B testing: Compare infrastructure configurations
- Emergency rollbacks: Quickly disable problematic features
Experiments (Opt-in Unstable Features)
Terragrunt provides an experiments system for trying unstable features before they’re GA:
# Enable all experiments (not recommended for production)
terragrunt --experiment-mode run --all plan
# Enable specific experiment
terragrunt --experiment symlinks run --all plan
# Enable CAS (Content Addressable Storage) for faster cloning
terragrunt --experiment cas run --all plan
Available Experiments:
symlinks- Support symlink resolution for Terragrunt unitscas- Content Addressable Storage for faster Git/module cloningfilter-flag- Advanced filtering capabilities (coming in 1.0)
Output Interpretation
Success Indicators
✅ All checks passing:
- All HCL files properly formatted
- Inputs are valid
- Terraform configuration is valid
- No linting issues
- No critical security issues
- Valid dependency graph
- Plan generated successfully
Warning Indicators
⚠️ Review needed:
- Security warnings from tfsec (non-critical)
- Linting suggestions (best practices)
- Deprecated provider features
- Missing recommended configurations
Error Indicators
✗ Must fix:
- Format errors
- Invalid inputs
- Terraform validation failures
- Circular dependencies
- Provider authentication failures
- State locking errors
Troubleshooting Guide
Debug Mode
Enable debug output for troubleshooting:
# Terragrunt debug
TERRAGRUNT_DEBUG=1 terragrunt plan
# Terraform trace
TF_LOG=TRACE terragrunt plan
Common Error Patterns
“Error: Module not found”
- Clear cache:
rm -rf .terragrunt-cache - Re-initialize:
terragrunt init
“Error: Provider not found”
- Check provider configuration
- Run custom resource detection
- Use WebSearch to find correct provider source and version
- Verify required_providers block
“Error: Invalid function call”
- Check Terragrunt version compatibility
- Review function syntax in documentation
“Cycle detected in dependency graph”
- Review dependency chains
- Consider refactoring into single module
- Use data sources instead of dependencies
“Error acquiring state lock”
- Check if another process is running
- Verify DynamoDB table (for S3 backend)
- Force unlock if safe:
terragrunt force-unlock <LOCK_ID>
“Error: unknown command” (Terragrunt 0.93+)
- Terragrunt 0.93+ has a new CLI with breaking changes
- Commands like
render-json,validate-inputsare deprecated - Use
terragrunt run -- <command>for custom/unsupported commands - Replace
graph-dependencieswithdag graph - See: https://terragrunt.gruntwork.io/docs/migrate/cli-redesign/
Terragrunt Version Compatibility
This skill is designed for Terragrunt 0.93+ which includes the new CLI redesign.
CLI Command Migration Reference
| Deprecated Command | New Command |
|---|---|
run-all |
run --all |
hclfmt |
hcl fmt |
hclvalidate |
hcl validate |
validate-inputs |
hcl validate --inputs |
graph-dependencies |
dag graph |
render-json |
render --json -w |
terragrunt-info |
info print |
plan-all, apply-all |
run --all plan, run --all apply |
Key Changes in 0.93+
terragrunt run --allreplacesterragrunt run-allfor multi-module operationsterragrunt dag graphreplacesterragrunt graph-dependenciesfor dependency visualizationterragrunt hcl validate --inputsreplacesvalidate-inputsfor input validation- HCL syntax validation via
terragrunt hcl fmt --checkorterragrunt hcl validate - Full validation requires
terragrunt init && terragrunt validate
If using an older Terragrunt version, some commands may need adjustment.