Terragrunt Infrastructure Skill
Overview
This skill provides guidance for infrastructure using Terragrunt with OpenTofu, following a three-repository pattern:
- Infrastructure Catalog - Units and stacks that reference modules from separate repos
- Infrastructure Live - Environment-specific deployments consuming the catalog
- Module Repos - Separate repositories for each OpenTofu module (independent versioning)
Choosing an Architecture
Default recommendation: explicit stacks — the catalog + terragrunt.stack.hcl + values pattern this skill teaches. Units are generated from stack files; configuration flows through values; no per-unit boilerplate.
Supported alternative: classic implicit stacks — the original Gruntwork pattern: account/region/env/component directory hierarchy, per-level .hcl variable files, shared component config in _envcommon/ via include + expose. Any directory of units is an implicit stack. Use it when the repo already follows it, when the footprint is too small to justify a catalog, or when the team isn't ready for stacks. See classic-live-structure.md.
Orthogonal choice: module organization — modules monorepo (modules/ + examples/, one tag versions all, //modules/x?ref= sourcing) vs module-per-repo (independent versioning). Either works with either architecture. See modules-monorepo.md.
Migrating classic → stacks: follow the official Terralith to Terragrunt guide.
Quick Navigation
| Topic | Reference |
|---|---|
| Naming conventions | naming.md |
| Catalog structure | catalog-structure.md |
| Live repo structure | live-structure.md |
| Classic live structure (implicit stacks) | classic-live-structure.md |
| Modules monorepo | modules-monorepo.md |
| Discovery commands (find/list/dag) | discovery-commands.md |
| Root/account/env configs | root-config.md |
| Unit dependencies | dependencies.md |
| Catalog scaffolding | catalog-scaffolding.md |
| Stack commands | stack-commands.md |
| Patterns & best practices | patterns.md |
| State management | state-management.md |
| Multi-account setup | multi-account.md |
| Performance optimization | performance.md |
| Content Addressable Store (CAS) | cas.md |
| CI/CD pipelines (shared + IAM/OIDC setup) | cicd-pipelines.md |
| GitLab CI pipelines | cicd-gitlab.md |
| GitHub Actions pipelines | cicd-github.md |
Core Concepts
Values Pattern
Units receive configuration through values.xxx:
inputs = {
name = values.name
environment = values.environment
instance_class = try(values.instance_class, "db.t3.medium") # Optional with default
}
Reference Resolution
Units resolve symbolic references like "../acm" to dependency outputs:
inputs = {
acm_certificate_arn = try(values.acm_certificate_arn, "") == "../acm" ?
dependency.acm.outputs.acm_certificate_arn :
values.acm_certificate_arn
}
Terragrunt 1.1+ adds autoinclude blocks as an alternative — dependencies declared in the stack file, catalog units stay dependency-agnostic. See dependencies.md for choosing between them.
Module Sourcing
Units reference modules via Git URL with version from values:
terraform {
source = "git::git@github.com:YOUR_ORG/modules/rds.git//app?ref=${values.version}"
}
For catalog-internal references (units/stacks/modules in the same repo), Terragrunt 1.1+ allows plain relative paths with update_source_with_cas = true — stack generate rewrites them to content-addressed references, so no URL pinning or version plumbing is needed. See cas.md.
Common Operations
Create New Unit
- Create
units/<name>/terragrunt.hcl - Reference module via Git URL with
${values.version} - Use
values.xxxfor inputs - Add dependencies with mock outputs
- Implement reference resolution for
"../unit"patterns
Create New Stack
- Create
stacks/<name>/terragrunt.stack.hcl - Define
localsfor computed values - Add
unitblocks referencing catalog units - Pass values including version and dependency paths
Deploy to New Environment
- Create environment directory structure
- Add
env.hclwithstate_bucket_suffix - Run
terragrunt run --all -- backend bootstrapto create state resources - Add stack files referencing catalog
Best Practices
- Pin module versions - Use Git tags in
values.version - Pin catalog versions - Use refs in unit source URLs
- Use reference resolution -
"../unit"→ dependency outputs - Provide mock outputs - Enable plan/validate without dependencies
- Auto-detect features -
length(keys(try(values.X, {}))) > 0 - Override paths -
try(values.X_path, "../default") - Separate state per environment - Use
state_bucket_suffix
Common Pitfalls
- Git refspec error - Use
//path?ref=branchNOT?ref=branch//path - Heredoc in ternary - Wrap in parentheses:
condition ? (\n<<-EOF\n...\nEOF\n) : "" - Missing mock outputs - Always provide for plan/validate
- Hardcoded paths - Use local paths only for testing
Version Management
- Development: Branch refs (
ref=feature-branch) - Testing: RC tags (
ref=v1.0.0-rc1) - Production: Stable tags (
ref=v1.0.0)