Tharindu Kalhara

2025-12-25

Getting Started with Terragrunt

terraformterragruntiac

Getting Started with Terragrunt

I hope you've heard about the Infrastructure as Code (IaC) tool Terraform. Recently, I had the opportunity to work with another powerful tool that complements Terraform: Terragrunt.

Terragrunt is a thin wrapper around Terraform that helps manage Terraform configurations more efficiently, especially when working with:

  • Multiple environments (Development, Testing, Staging, Production)
  • Multiple cloud accounts
  • Multiple regions

Its primary goal is to eliminate code duplication while simplifying configuration management and remote state handling.


Why Terragrunt Exists

As Terraform projects grow, several common challenges appear:

  • Repeating the same backend configuration in multiple folders
  • Copy-pasting variables across environments
  • Managing remote state manually
  • Handling dependencies between Terraform modules

Terragrunt addresses these problems by providing a cleaner and more maintainable project structure.


What Terragrunt Does

1. DRY (Don't Repeat Yourself)

Instead of writing the same Terraform configuration repeatedly:

  • Create Terraform modules once
  • Reuse them across multiple environments using Terragrunt

2. Remote State Management

Terragrunt automatically configures remote backends such as:

  • Azure Storage
  • AWS S3
  • DynamoDB (State Locking)

This removes the need to define backend blocks inside every Terraform module.


3. Environment Separation

Maintain a clean directory structure for environments such as:

  • Development
  • Staging
  • Production

Each environment can reuse the same Terraform modules while supplying different inputs.


4. Dependency Management

Terragrunt allows modules to depend on one another by:

  • Defining module dependencies
  • Passing outputs between modules safely

5. Infrastructure Orchestration

Execute operations across multiple modules with a single command.

Examples:

  • terragrunt apply
  • terragrunt destroy

This is especially useful for large infrastructure deployments.


Prerequisites

Before getting started, install the following:

  • Azure CLI (if using Azure)
  • Terraform Version Manager (tfenv)
  • Terragrunt Version Manager (tgenv)

Additionally, create the required remote state resources beforehand.

For Azure:

  • Resource Group
  • Storage Account

For AWS:

  • S3 Bucket
  • DynamoDB Table (for state locking)

Step 1 - Create the Root terragrunt.hcl

locals {
  default_yaml_path = find_in_parent_folders("empty.yaml")
  org = yamldecode(file(find_in_parent_folders("org.yaml")))
  subscription = yamldecode(file(find_in_parent_folders("subscription.yaml")))
  resource_group = yamldecode(file(find_in_parent_folders("resource_group.yaml")))
  environment = yamldecode(file(find_in_parent_folders("environment.yaml", local.default_yaml_path)))
}

generate "provider" {
  path = "provider.tf"
  if_exists = "overwrite_terragrunt"

  contents = <<EOF
provider "azurerm" {
  features {}
  subscription_id = "${local.subscription.subscription_id}"
}
EOF
}

remote_state {
  backend = "azurerm"

  generate {
    path = "backend.tf"
    if_exists = "overwrite"
  }

  config = {
    subscription_id = local.subscription.state_subscription
    resource_group_name = local.subscription.state_resource_group
    storage_account_name = local.subscription.state_storage_account
    container_name = "terraform-state-v2"
    key = "${path_relative_to_include()}/terraform.tfstate"
  }
}

inputs = merge(
  local.org,
  local.subscription,
  local.resource_group,
  local.environment
)

Step 2 - Create org.yaml

org_name: your_org_name_azure

Step 3 - Create empty.yaml

{}

Step 4 - Directory Structure

Create a directory for your subscription, followed by resource groups and infrastructure resources.

subscriptions/
└── Tharinduksubscription/
    ├── resource-groups/
    │   └── terra-rg/
    │       ├── buckets/
    │       │   └── terra-bucket/
    │       ├── resourcegroup/
    │       ├── resource_group.yaml
    │       ├── subscription.yaml
    │       ├── .gitignore
    │       ├── README.md
    │       ├── empty.yaml
    │       ├── org.yaml
    │       └── terragrunt.hcl

Step 5 - Configure subscription.yaml

This file contains the configuration required for remote state management.

state_subscription:
state_resource_group: terraform-state
state_storage_account:
subscription_id:

Step 6 - Configure resource_group.yaml

location: uksouth
resource_group_name: terra-rg

Step 7 - Create Resource Modules

Each infrastructure resource should have its own directory.

Example structure:

resourcegroup/
├── terragrunt.hcl
├── variables.tf
└── resourcegroup.tf

Resource Definition

resource "azurerm_resource_group" "resource_group" {
  location = var.location
  name     = var.resource_group_name
}

terragrunt.hcl

include {
  path = find_in_parent_folders()
}

variables.tf

variable "location" {}

variable "resource_group_name" {}

Step 8 - Deploy Infrastructure

Once everything is configured, execute:

terragrunt plan

Then deploy:

terragrunt apply

Terragrunt will automatically:

  • Configure the backend
  • Generate provider configuration
  • Read shared variables
  • Execute Terraform

Conclusion

Terragrunt significantly simplifies managing Terraform infrastructure by eliminating repetitive configuration, organizing environments, and automating remote state management.

Although the initial setup requires some patience, it greatly improves maintainability as infrastructure grows. Once configured, managing multiple environments becomes much cleaner and more scalable.


Key Benefits

  • Less code duplication
  • Easier multi-environment management
  • Automatic remote state configuration
  • Cleaner project structure
  • Built-in dependency management
  • Better scalability for large infrastructure projects