IaCInfrastructure as Codeterraformterragrunt

When Terraform Is No Longer Enough: Introducing Terragrunt

Terraform is powerful, but managing multiple environments and modules at scale quickly becomes challenging. In this article, we'll explore Terraform's limitations and why Terragrunt was created.

Jul 22, 20268 min readUpdated Jul 22, 2026
When Terraform Is No Longer Enough: Introducing Terragrunt

When Terraform Is No Longer Enough: Introducing Terragrunt

Throughout this series, we've covered a wide range of Terraform concepts.

From the fundamentals of Infrastructure as Code, how Terraform works, Terraform State, Plan & Apply, Variables, Data Sources, Remote State, to designing a production-ready Terraform repository.

Looking at everything Terraform provides, you might think:

"Terraform is enough to build any infrastructure."

In reality...

Yes, but not quite.

Terraform is an incredibly powerful tool for provisioning and managing infrastructure.

However, Terraform was never designed to solve every problem related to managing multiple environments, multiple cloud accounts, and hundreds of infrastructure modules.

That's why almost every organization using Terraform at scale introduces another layer on top of it.

One of the most popular solutions is Terragrunt.


Terraform Excels at Provisioning Infrastructure

Terraform is excellent at its core responsibilities:

  • Managing resources
  • Tracking infrastructure state
  • Calculating dependencies
  • Building execution plans
  • Applying infrastructure changes safely

For example:

code
resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
}
 
resource "aws_subnet" "public" {
  vpc_id = aws_vpc.main.id
}

Terraform understands that:

VPC
 ↓
Subnet

It automatically creates the resources in the correct order.

If only the subnet changes:

Plan
↓

Update Subnet

Leave VPC unchanged

When resources depend on one another:

IAM Role
     ↓
EC2
     ↓
Load Balancer

Terraform automatically builds the dependency graph and executes everything in the proper sequence.

This is one of Terraform's greatest strengths.


But Production Is More Than One Environment

When you're learning Terraform, most examples look like this:

terraform-project/

main.tf
variables.tf
outputs.tf

One directory.

One environment.

One state file.

This is perfectly fine for learning.

However, once you move into production, things change quickly.

For example:

Production

AWS Account A
    ├── VPC
    ├── Network
    ├── EKS
    ├── RDS
    └── Monitoring

Staging

AWS Account B
    ├── VPC
    ├── Network
    ├── EKS
    ├── RDS
    └── Monitoring

Development

AWS Account C
    ├── VPC
    ├── Network
    ├── EKS
    ├── RDS
    └── Monitoring

Suddenly, you're no longer managing:

  • one environment

Instead, you're managing:

  • multiple environments
  • multiple AWS accounts
  • multiple regions
  • dozens of Terraform modules

Repetition Starts to Appear

Imagine you've already built a reusable VPC module.

modules/
    vpc/

Now you want to use it in three different environments:

dev
staging
production

A common repository structure looks like this:

live/

dev/
    vpc/

staging/
    vpc/

production/
    vpc/

Inside each directory, you'll often find almost identical Terraform code.

Development:

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

Staging:

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

Production:

code
module "vpc" {
  source = "../../modules/vpc"
 
  cidr = "10.2.0.0/16"
 
  environment = "production"
}

At first glance, this doesn't seem like a problem.

But as the number of modules grows:

  • VPC
  • IAM
  • EKS
  • RDS
  • Redis
  • S3
  • CloudFront
  • Route53
  • Monitoring
  • Logging
  • WAF
  • Secrets

...

Every environment ends up duplicating almost the same configuration.


It's Not Just About Duplicate Code

The bigger issue is the shared configuration.

For example, backend configuration:

code
terraform {
  backend "s3" {
    bucket = "company-tfstate"
  }
}

Provider configuration:

code
provider "aws" {
  region = "ap-southeast-1"
}

Common tags:

code
tags = {
  Team = "Platform"
  Owner = "Infrastructure"
}

Remote State configuration:

code
data "terraform_remote_state" ...

Provider versions:

code
terraform {
  required_providers {
    aws = {
      version = "~> 6.0"
    }
  }
}

These configuration blocks appear in nearly every module.

If your infrastructure contains 50 modules across 4 environments, you could easily end up maintaining hundreds of Terraform files that are nearly identical.

That's the point where maintaining the repository starts becoming expensive.

Terraform Does Not Understand "Environments"

One interesting detail is that Terraform has no built-in concept of:

  • Development
  • Staging
  • Production

Terraform only understands the current:

Working Directory

For example:

live/

dev/
    app/

staging/
    app/

production/
    app/

From Terraform's perspective, these three directories are completely independent.

Terraform does not understand that:

dev
 ↓
staging
 ↓
production

represent different versions of the same system.

Terraform also does not know:

  • which environments belong to the same project
  • which account belongs to production
  • which region is being used
  • which configuration should be shared between modules

Everything must be organized by the engineers maintaining the repository.

This provides a great deal of flexibility, but it also makes the system harder to manage as the infrastructure grows.


Terraform Does Not Manage Your Repository Structure

Terraform only reads the current directory.

For example:

code
terraform apply

Terraform reads the files available in:

.
├── main.tf
├── variables.tf
└── outputs.tf

It does not care whether the repository is organized as:

company-iac/

projects/
platform/
network/
production/

or:

live/
modules/
shared/

Terraform does not enforce:

  • directory naming conventions
  • module boundaries
  • environment organization
  • account organization
  • backend management
  • repository architecture

This means two companies can both use Terraform while having completely different repository structures.

For example:

Company A:

production/
staging/
development/

Company B:

aws/
azure/
gcp/

Company C:

platform/
application/
security/
network/

All of these structures are valid.

Terraform does not validate or manage any of them.


What Happens as the System Grows?

Imagine an organization with:

  • 4 AWS accounts
  • 3 regions
  • 6 environments
  • 80 Terraform modules

If every module contains:

main.tf
providers.tf
backend.tf
versions.tf
variables.tf

the repository can quickly grow to hundreds of files.

The important part is that much of their content will be almost identical.

For example, the backend configuration may be repeated:

code
terraform {
  backend "s3" {
    bucket = "company-state"
  }
}

The provider configuration may be repeated:

code
provider "aws" {
  region = "ap-southeast-1"
}

The required Terraform version may be repeated:

code
terraform {
  required_version = ">= 1.8"
}

The same tags may also appear everywhere:

code
tags = {
  ManagedBy = "Terraform"
}

Now imagine that the organization needs to:

  • change the State bucket
  • upgrade the provider version
  • add a mandatory tag
  • update its Assume Role configuration

A small organizational change may require updates across dozens or even hundreds of directories.

This is not necessarily a flaw in Terraform.

Terraform was simply never designed to manage repository-wide conventions and shared configuration.


How Teams Tried to Solve This Problem

Before Terragrunt became popular, many teams built their own solutions.

Some used symbolic links:

backend.tf
    ↓
symbolic links across multiple directories

Others created scripts that copied shared files:

copy backend.tf
    ↓
100 directories

Some teams used Bash:

code
for dir in */; do
  cp backend.tf "$dir"
done

Others introduced Makefiles:

code
apply-all:

Some wrote custom Python wrappers:

code
python deploy.py

Larger organizations sometimes built complete internal platforms around Terraform.

All of these approaches were trying to solve the same problem:

Reduce duplicated configuration and make large Terraform repositories easier to manage.

This shows that the problem was real and common across many teams.

Terragrunt was created to address these exact challenges.


Terragrunt Does Not Replace Terraform

This is one of the most common misunderstandings among people who are new to Terragrunt.

Terragrunt is not a newer version of Terraform.

Terragrunt also does not create infrastructure resources by itself.

It does not directly call AWS APIs.

It does not manage Terraform State.

It does not build Terraform's resource dependency graph.

It does not provision infrastructure.

The actual execution flow is still:

Terragrunt
    ↓
Terraform
    ↓
Cloud Provider

In other words, Terragrunt sits in front of Terraform.

It helps with tasks such as:

  • preparing Terraform configuration
  • organizing repositories
  • reducing duplicated code
  • passing input variables
  • managing multiple modules
  • managing multiple environments

Terraform is still the tool that ultimately executes plan and apply.

That is why understanding Terraform remains essential before using Terragrunt effectively.

Terragrunt was created to complement Terraform, not replace it.

What Problems Does Terragrunt Solve?

If I had to describe Terragrunt in a single sentence, it would be:

Terragrunt is a wrapper that makes Terraform easier to use in real-world infrastructure projects.

It does not change how Terraform works.

Instead, it eliminates many of the repetitive tasks that Terraform intentionally leaves to users.

For example, instead of defining the backend configuration in every module:

code
terraform {
  backend "s3" {
    bucket = "company-state"
    key    = "..."
    region = "ap-southeast-1"
  }
}

Terragrunt allows you to define it once and reuse it across multiple modules.

The same idea applies to:

  • Providers
  • Common variables
  • Common tags
  • Remote State configuration
  • Provider versions
  • Naming conventions

This makes large infrastructure repositories much easier to maintain.


Key Capabilities of Terragrunt

In the following articles, we'll explore each of these features in detail.

For now, it's enough to understand that Terragrunt helps with several important aspects of infrastructure management.

Reducing Duplicate Configuration

A shared configuration can be defined once and inherited throughout the repository.

Root Configuration
        │
        ├── Development
        ├── Staging
        └── Production

Instead of copying the same HCL blocks into dozens of directories.


Managing Multiple Environments

A typical repository may contain:

live/

dev/

staging/

production/

Terragrunt allows these environments to inherit a common base configuration while still overriding environment-specific values where necessary.


Managing Multiple Accounts and Regions

For example:

AWS

Production
    ap-southeast-1
    us-east-1

Development
    ap-southeast-1

Terragrunt makes organizing these structures significantly cleaner and easier to understand.


Running Multiple Modules in the Correct Order

Imagine an infrastructure like this:

Network
     ↓
Database
     ↓
EKS
     ↓
Applications

Terragrunt can orchestrate the execution of multiple modules in the correct dependency order, eliminating much of the manual work involved in applying infrastructure changes.


Standardizing Repository Structure

One of Terragrunt's biggest advantages is helping an entire engineering team follow the same repository conventions.

Instead of every engineer inventing their own directory structure and workflow, the whole team can share a consistent architecture that's easier to maintain and scale over time.


Does Every Project Need Terragrunt?

The answer is:

No.

If your project only has:

  • one environment
  • a handful of resources
  • one or two modules

then plain Terraform is usually the simplest and most effective solution.

Adding Terragrunt at this stage may introduce unnecessary complexity.

On the other hand, once your infrastructure grows to include:

  • multiple environments
  • multiple cloud accounts
  • multiple regions
  • multiple engineering teams
  • dozens or hundreds of modules

managing everything with Terraform alone becomes increasingly difficult.

That's exactly where Terragrunt starts providing significant value.

The important takeaway is:

Don't adopt Terragrunt simply because everyone else uses it.

Use it when your infrastructure has reached the point where the problems Terragrunt solves actually exist.


Summary

Terraform is an outstanding infrastructure provisioning tool.

However, Terraform focuses on provisioning infrastructure, not on managing the overall structure of a large infrastructure repository.

As the number of modules, environments, cloud accounts, and teams grows, duplicated configuration and repository management become increasingly challenging.

Terragrunt addresses these challenges by adding a management layer on top of Terraform, helping teams reduce duplication, standardize repository structure, and simplify large-scale Infrastructure as Code operations.


Next Article

In the next article, we'll officially begin our Terragrunt journey by answering the most fundamental question:

What is Terragrunt, and how does it work?

We'll explore Terragrunt's architecture, understand how it works alongside Terraform, and build our very first Terragrunt project to see how it fits into a modern Infrastructure as Code workflow.

Comments

0/2000

Loading comments…