Back to articles
4 min read

Speed up Terraform with Terragrunt, end the endless applies

Separate state files, no copy-paste, no re-running everything. Terragrunt fixes what Terraform doesn't want to.

TerraformTerragruntDevOpsIaC

Waiting 10 minutes for terraform apply to check 47 resources when I changed one database parameter

Copying the same backend config into 15 different directories. Every single time

People saying ‘you can do this with pure Terraform’ while they rebuild Terragrunt badly. Sure

Terragrunt holding Terraform

The problem Terraform won’t fix

Terraform works great for one thing. It falls apart when you have multiple environments, multiple regions, multiple anything

Run terraform apply in a monolithic state file. It checks every single resource. You changed a DNS record? Cool, let me verify your entire VPC, every EC2 instance, all your RDS databases, and that S3 bucket you created 6 months ago

Split it into modules to avoid the monolith. Now you’re copying backend configs, variable definitions, and provider blocks everywhere. DRY goes out the window

The Terraform purists say ‘just use modules and workspaces.’ Workspaces share the same state file. You’re back to the 10-minute apply. Modules without Terragrunt? You write the same boilerplate in every environment:

# dev/main.tf
terraform {
  backend 's3' {
    bucket = 'my-terraform-state'
    key    = 'dev/terraform.tfstate'
    region = 'us-east-1'
  }
}

module 'vpc' {
  source = '../modules/vpc'
  environment = 'dev'
  cidr = '10.0.0.0/16'
}

# staging/main.tf
terraform {
  backend 's3' {
    bucket = 'my-terraform-state'
    key    = 'staging/terraform.tfstate'
    region = 'us-east-1'
  }
}

module 'vpc' {
  source = '../modules/vpc'
  environment = 'staging'
  cidr = '10.1.0.0/16'
}

# prod/main.tf
terraform {
  backend 's3' {
    bucket = 'my-terraform-state'
    key    = 'prod/terraform.tfstate'
    region = 'us-east-1'
  }
}

module 'vpc' {
  source = '../modules/vpc'
  environment = 'prod'
  cidr = '10.2.0.0/16'
}

Three environments, three copies of the same code. Change your S3 bucket name? Update it in 47 places. Miss one? Your state file ends up in the wrong place

What Terragrunt actually does

Terragrunt gives you separate state files without the copy-paste hell

One backend config in the root:

# root.hcl
remote_state {
  backend = 's3'
  config = {
    bucket = 'my-terraform-state'
    key    = '${path_relative_to_include()}/terraform.tfstate'
    region = 'us-east-1'
  }
}

Each environment references it:

# dev/terragrunt.hcl
include 'root' {
  path = find_in_parent_folders('root.hcl')
}

inputs = {
  environment = 'dev'
  cidr = '10.0.0.0/16'
}

That’s it. No backend block duplication. No copy-paste. The state file path is automatic based on directory structure

Change your backend config once, it applies everywhere. Add encryption? One line in the root config

Dependencies that actually work

Terraform has depends_on. It’s manual and you forget it until everything breaks

Terragrunt has dependency blocks that are explicit:

# app/terragrunt.hcl
dependency 'vpc' {
  config_path = '../vpc'
}

dependency 'database' {
  config_path = '../database'
}

inputs = {
  vpc_id = dependency.vpc.outputs.vpc_id
  db_endpoint = dependency.database.outputs.endpoint
}

Run terragrunt apply in the app directory. It knows it needs the VPC and database first. It applies them in order. No manual orchestration

The output from one module feeds directly into the next. No manual variable passing. No hoping you remembered the right order

Run once, not 47 times

Split your infrastructure into logical pieces:

  • vpc/ has its own state
  • database/ has its own state
  • app/ has its own state

Change the database? terragrunt apply in database/ only. Takes 30 seconds instead of 10 minutes

The VPC state file doesn’t get touched. The app state file doesn’t get touched. Only what you changed gets checked

I split a monolithic Terraform setup into Terragrunt modules. Apply times went from 8 minutes to under 1 minute for most changes. The VPC hasn’t changed in months, why would I check it?

The ‘you can do this in Terraform’ crowd

They’re right. Technically you can. You’ll write bash scripts to generate backend configs. You’ll use Makefiles to run applies in the right order. You’ll create your own dependency management

You’re rebuilding Terragrunt. Badly

I’ve seen teams do this. They end up with:

  • A bash script that templates out backend configs
  • A Python script that parses outputs and generates variable files
  • A Makefile with 47 targets in the correct order
  • Documentation that’s 3 months out of date
  • New devs who have no idea how any of it works

Or you can install Terragrunt and write a few terragrunt.hcl files

Real structure that scales

infrastructure/
├── root.hcl                    # Root config, backend settings
├── dev/
│   ├── vpc/
│   │   └── terragrunt.hcl
│   ├── database/
│   │   └── terragrunt.hcl
│   └── app/
│       └── terragrunt.hcl
├── staging/
│   ├── vpc/
│   │   └── terragrunt.hcl
│   ├── database/
│   │   └── terragrunt.hcl
│   └── app/
│       └── terragrunt.hcl
└── modules/
    ├── vpc/
    ├── database/
    └── app/

Each terragrunt.hcl is 10-20 lines. The actual Terraform code lives in modules/. No duplication

Want to apply everything in dev? terragrunt run-all apply from dev/. It figures out dependencies and runs them in order

Want to apply just the database? cd dev/database && terragrunt apply. Takes seconds

What Terragrunt doesn’t fix

HCL is still limited. You can’t do complex logic without hacks

Terraform’s state management is still fragile. Terragrunt just makes it manageable

The Terraform registry modules are still hit or miss. Terragrunt doesn’t fix bad modules

But it fixes the problems that made me want to quit Terraform:

  • Waiting forever for applies
  • Copying config everywhere
  • Manual dependency tracking
  • Hoping I didn’t break prod by forgetting the right apply order

When not to use Terragrunt

Small projects with one environment. Just use Terraform. Terragrunt adds complexity you don’t need

If your team refuses to learn anything new. Terragrunt has a learning curve. Not steep, but it exists

If you’re deploying to 47 different clouds with wildly different patterns. Terragrunt expects some structure

Getting started

Install Terragrunt:

# Debian/Ubuntu
wget https://github.com/gruntwork-io/terragrunt/releases/latest/download/terragrunt_linux_amd64
sudo mv terragrunt_linux_amd64 /usr/local/bin/terragrunt
sudo chmod +x /usr/local/bin/terragrunt

# Arch
yay -S terragrunt

Create a root.hcl:

remote_state {
  backend = 's3'
  config = {
    bucket = 'your-terraform-state'
    key    = '${path_relative_to_include()}/terraform.tfstate'
    region = 'us-east-1'
    encrypt = true
  }
}

Create an environment config:

# dev/vpc/terragrunt.hcl
include 'root' {
  path = find_in_parent_folders('root.hcl')
}

terraform {
  source = '../../modules/vpc'
}

inputs = {
  environment = 'dev'
  cidr = '10.0.0.0/16'
}

Run it:

cd dev/vpc
terragrunt init
terragrunt apply

Your backend is configured automatically. Your state file is in the right place. No copy-paste needed

I spent a year fighting Terraform’s limitations. I spent a week learning Terragrunt. I’m not going back

Reality is often more nuanced. But me? Nuance bores me. I'd rather be clear.

Comments