Free SKILL.md scraped from GitHub. Clone the repo or copy the file directly into your Claude Code skills directory.
npx versuz@latest install event4u-app-agent-config-agent-src-skills-terragruntgit clone https://github.com/event4u-app/agent-config.gitcp agent-config/SKILL.MD ~/.claude/skills/event4u-app-agent-config-agent-src-skills-terragrunt/SKILL.md---
name: terragrunt
description: "Use when working with Terragrunt — DRY multi-env configs, module dependencies, remote state orchestration — even when the user just says 'deploy this to staging and prod' without naming Terragrunt."
source: package
domain: devops
---
# terragrunt
## When to use
Use this skill when working with Terragrunt configurations (`.hcl` files), managing environment-specific settings, or orchestrating multi-module deployments.
## Procedure: Write Terragrunt config
1. Read the `root.hcl` in the target env (`environments/pro/root.hcl` or `environments/sta/root.hcl`).
2. Check existing `terragrunt.hcl` files in sibling directories for patterns.
3. Read the target module's `variables.tf` to understand required inputs.
## Project structure
```
environments/
├── pro/
│ ├── root.hcl # Root config (backend, providers)
│ ├── core/
│ │ ├── terragrunt.hcl # Core module config
│ │ └── {service}.yaml # Module-specific variables
│ ├── {service}/
│ │ ├── terragrunt.hcl # Service module config
│ │ └── {service}.yaml # Service-specific variables
│ └── ...
└── sta/
├── root.hcl
└── ...
```
## Root configuration (`root.hcl`)
The root HCL defines shared settings for all modules in an env:
### Environment variables
```hcl
locals {
env_files = merge(
yamldecode(file(".env.yaml")), # Base env vars
try(yamldecode(file(".env.local.yaml")), {}) # Local overrides
)
env = { for k, v in local.env_files : k => get_env(k, v) }
}
```
- `.env.yaml` — committed, shared env config
- `.env.local.yaml` — gitignored, local overrides (AWS profiles, etc.)
- Real env variables take precedence over file values.
### Remote state
```hcl
remote_state {
backend = "s3"
config = {
bucket = "${local.env.aws_account_name}-terraform-remote-state"
key = "${path_relative_to_include()}/terraform.tfstate"
dynamodb_table = "${local.env.aws_account_name}-terraform-remote-state"
profile = local.env.aws_profile
region = local.env.aws_region
encrypt = true
}
}
```
### Provider generation
Providers are auto-generated by Terragrunt (not manually written):
```hcl
generate "provider" {
path = "provider-aws.tf"
if_exists = "overwrite_terragrunt"
contents = <<EOF
provider "aws" {
allowed_account_ids = ["${local.env.aws_account_id}"]
profile = "${local.env.aws_profile}"
region = "${local.env.aws_region}"
}
EOF
}
```
### Terraform binary
```hcl
terraform_binary = "terraform" # Explicitly NOT OpenTofu
```
## Module configuration (`terragrunt.hcl`)
Each module directory contains a `terragrunt.hcl` that:
1. **Loads module-specific variables** from a YAML file
2. **Includes the root config** for backend and providers
3. **Points to the Terraform module source**
4. **Declares deps** on other modules
5. **Passes inputs** by merging dependency outputs with local variables
### Example pattern
```hcl
locals {
project = yamldecode(file("{service}.yaml"))
}
include {
path = find_in_parent_folders("root.hcl")
expose = true
}
terraform {
source = "../../..//modules/{service}"
}
dependency "core" {
config_path = "../core"
}
inputs = merge(
dependency.core.outputs,
local.project
)
```
## Conventions
### Variable files
- Use **YAML files** (not HCL) for module-specific variables.
- Name them after the module (e.g., `my-service.yaml`).
- Keep them in the same directory as `terragrunt.hcl`.
### Dependencies
- Use `dependency` blocks to reference other modules.
- Core module outputs (VPC, DNS zones, etc.) are passed via `dependency.core.outputs`.
- Merge dependency outputs with local variables in `inputs`.
### Additional providers
Some modules need extra providers (e.g., New Relic). Generate them in the module's `terragrunt.hcl`:
```hcl
generate "provider_newrelic" {
path = "provider-newrelic.tf"
if_exists = "overwrite_terragrunt"
contents = <<EOF
provider "newrelic" {
account_id = "${include.locals.env.newrelic_account_id}"
api_key = "${get_env("TF_VAR_newrelic_api_key", include.locals.env.newrelic_api_key)}"
}
EOF
}
```
## Development tools
The project uses **devbox** for tool management:
```json
{
"packages": ["terragrunt", "awscli2", "terraform@latest"]
}
```
### Quick commands (devbox scripts)
```bash
devbox run i # terragrunt init
devbox run p # terragrunt plan
devbox run a # terragrunt apply
devbox run d # terragrunt destroy
```
## Output format
1. Terragrunt HCL files with DRY env configuration
2. Dependency graph and remote state references
## Auto-trigger keywords
- Terragrunt
- multi-env
- DRY config
- remote state
## Gotcha
- Terragrunt `dependency` blocks create implicit ordering — circular deps cause cryptic errors.
- Don't duplicate Terraform variables in terragrunt.hcl — use `inputs` to pass them through.
- The model tends to hardcode values that should come from `include` blocks — use DRY patterns.
## Do NOT
- Do NOT switch to OpenTofu — the project explicitly uses `terraform_binary = "terraform"`.
- Do NOT hardcode AWS credentials — use AWS profiles from `.env.yaml`.
- Do NOT commit `.env.local.yaml` — it contains local AWS profile overrides.
- Do NOT modify `root.hcl` without understanding the impact on all modules.
- Do NOT remove `dependency` blocks — they ensure correct apply order.
- Do NOT use `terraform` commands directly — always use `terragrunt` as the wrapper.