Screenshot from the article

Terragrunt is a powerful tool that simplifies and enhances Terraform usage by providing better code reusability, DRY (Don’t Repeat Yourself) configurations, and easier multi-environment deployments. In this guide, we’ll explore how to install, configure, and leverage Terragrunt for complex infrastructure deployments.

In this guide, we’ll walk through:

  • Installing and configuring Terragrunt
  • Creating DRY Terraform configurations using Terragrunt
  • Advanced examples including dependency management, multiple environments, and hooks
  • FAQs with examples to help you master Terragrunt and prepare for technical interviews

Prerequisites

Before getting started, ensure you have:

Step 1: Installing Terragrunt

macOS (using Homebrew)

brew install terragrunt

Linux

Download the latest release from the Terragrunt GitHub Releases page, then extract and move it to your PATH:

wget https://github.com/gruntwork-io/terragrunt/releases/download/v0.47.0/terragrunt_linux_amd64
chmod +x terragrunt_linux_amd64
sudo mv terragrunt_linux_amd64 /usr/local/bin/terragrunt

Verify your installation:

terragrunt --version

Step 2: Organizing Your Terraform Code with Terragrunt

Terragrunt helps you avoid repetition by maintaining a single DRY configuration file that is included across multiple Terraform modules. Consider the following folder structure for multiple environments:

infrastructure/
├── live
│   ├── dev
│   │   ├── network
│   │   │   └── terragrunt.hcl
│   │   └── compute
│   │       └── terragrunt.hcl
│   └── prod
│       ├── network
│       │   └── terragrunt.hcl
│       └── compute
│           └── terragrunt.hcl
└── modules
    ├── network
    │   └── main.tf
    └── compute
        └── main.tf

Root Terragrunt Configuration

Create a root configuration (e.g., infrastructure/live/terragrunt.hcl) to set common settings for all environments:

# Root terragrunt.hcl
remote_state {
  backend = "s3"
  config = {
    bucket         = "my-terraform-state-bucket"
    key            = "${path_relative_to_include()}/terraform.tfstate"
    region         = "us-west-2"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}
locals {
  common_tags = {
    Project = "TerragruntDemo"
  }
}

Each child module (e.g., network/terragrunt.hcl) includes the root configuration:

# infrastructure/live/dev/network/terragrunt.hcl
include {
  path = find_in_parent_folders()
}
terraform {
  source = "../../../modules/network"
}
inputs = {
  environment = "dev"
  tags        = local.common_tags
}

Step 3: Advanced Terragrunt Examples

1. Managing Dependencies

Terragrunt can automatically pass outputs from one module as inputs to another using the dependency block.

# infrastructure/live/dev/compute/terragrunt.hcl
include {
  path = find_in_parent_folders()
}
dependency "network" {
  config_path = "../network"
}
terraform {
  source = "../../../modules/compute"
}
inputs = {
  environment = "dev"
  vpc_id      = dependency.network.outputs.vpc_id
  tags        = local.common_tags
}

2. Using Hooks for Pre/Post-Processing

terraform {
  source = "../../../modules/compute"
}
before_hook "validate" {
  commands = ["apply", "plan"]
  execute  = ["bash", "${get_terragrunt_dir()}/../../scripts/validate.sh"]
}

3. Advanced Remote State Configuration

# infrastructure/live/prod/network/terragrunt.hcl
include {
  path = find_in_parent_folders()
}
terraform {
  source = "../../../modules/network"
}
inputs = {
  environment = "prod"
  tags        = local.common_tags
}
remote_state {
  config = merge(
    local.remote_state_config,
    { key = "prod/network/terraform.tfstate" }
  )
}

Step 4: Running Terragrunt Commands

terragrunt init
terragrunt plan
terragrunt apply
terragrunt destroy

FAQs:

Q1: What is Terragrunt and why should I use it?

A: It helps keep Terraform configurations DRY (Don’t Repeat Yourself), manage remote state, and handle dependencies.

Q2: How does Terragrunt dependency management work?

A: Using the dependency block, Terragrunt ensures that one module’s outputs are available to another.

Example:

dependency "network" {
  config_path = "../network"
}
inputs = {
  vpc_id = dependency.network.outputs.vpc_id
}

Q3: How do hooks work in Terragrunt?

A: Hooks let you run custom scripts before or after Terraform commands.

Example:

before_hook "validate" {
  commands = ["apply", "plan"]
  execute  = ["bash", "${get_terragrunt_dir()}/../../scripts/validate.sh"]
}

Q4: How does Terragrunt simplify remote state management?

A: It centralizes state configuration in a root terragrunt.hcl file, avoiding repetition.

Q5: How can I troubleshoot issues with Terragrunt?

A: Run Terraform commands with increased logging:

export TF_LOG=DEBUG
terragrunt plan

Conclusion

Terragrunt elevates your Terraform workflow by reducing duplication, managing remote state effortlessly, and handling module dependencies with ease.

Key Takeaways:

✅ Install and set up Terragrunt

✅ Organize infrastructure code for multiple environments

✅ Use dependency management and hooks

✅ Address common questions with examples

If you found this guide helpful, follow me for more DevOps and Infrastructure as Code tutorials! 🚀

📢 Have questions or feedback? Drop a comment below or connect with me on Twitter/X@spysood!

Originally published on Medium.