mailwal

Still Copying Terraform Configurations Across Environments? There’s a Better Way with Terragrunt

Arvind Verma

Still Copying Terraform Configurations Across Environments? There’s a Better Way with Terragrunt

A practical guide to managing Dev, Staging, and Production infrastructure without duplicating Terraform code.

Imagine you’re managing AWS infrastructure for an application.

You have three environments:

  • Development

  • Staging

  • Production

All three environments need almost the same infrastructure: VPC, subnets, security groups, EC2 instances, and S3 buckets.

Initially, you create Terraform configurations for Dev. Then you copy the same files into Staging and Production.

Everything works.

But a few months later, someone asks you to add a new security group rule.

Now you need to update three different configurations.

You make the change in Dev, forget Staging, and accidentally configure Production differently.

This is where infrastructure management starts becoming difficult.

The problem isn’t Terraform. The problem is duplicated configuration and poor environment management.

One solution worth understanding is Terragrunt.

Let’s understand how Terraform and Terragrunt work together using a practical AWS example.

1. First, Understand Terraform vs. Terragrunt

What is Terraform?

Terraform is an Infrastructure as Code tool that allows you to provision and manage infrastructure using configuration files.

For example, you can use Terraform to create:

  • AWS VPCs

  • EC2 instances

  • S3 buckets

  • IAM roles

  • Security groups

  • EKS clusters

  • RDS databases

You describe the desired infrastructure, and Terraform compares that configuration with the current infrastructure state.

Example:

resource "aws_s3_bucket" "app" {
  bucket = "my-dev-app-bucket"
}

This tells Terraform to manage an S3 bucket.

What is Terragrunt?

Terragrunt is a tool that works with Terraform and OpenTofu to help manage infrastructure configurations, especially across multiple environments and modules.

It provides features such as:

  • Reusing Terraform modules

  • Managing environment-specific inputs

  • Configuring remote state

  • Reducing repeated backend configuration

  • Managing dependencies between infrastructure components

  • Coordinating operations across multiple Terraform modules

Think of it this way:

Terraform builds and manages infrastructure. Terragrunt helps organize and coordinate how Terraform configurations are used.

Terragrunt does not replace Terraform, and it does not eliminate every repeated configuration.

2. The Problem: Managing Environments with Plain Terraform

Suppose you have this folder structure:

terraform/
│
├── dev/
│   ├── main.tf
│   ├── variables.tf
│   └── outputs.tf
│
├── staging/
│   ├── main.tf
│   ├── variables.tf
│   └── outputs.tf
│
└── prod/
    ├── main.tf
    ├── variables.tf
    └── outputs.tf

You have three separate copies of almost identical Terraform configurations.

For example:

Dev:

resource "aws_instance" "app" {
  ami           = "ami-12345678"
  instance_type = "t3.micro"

  tags = {
    Environment = "dev"
  }
}

Staging:

resource "aws_instance" "app" {
  ami           = "ami-12345678"
  instance_type = "t3.small"

  tags = {
    Environment = "staging"
  }
}

Production:

resource "aws_instance" "app" {
  ami           = "ami-12345678"
  instance_type = "t3.large"

  tags = {
    Environment = "prod"
  }
}

Notice something?

The resource definition is almost identical. Only a few values change.

As your infrastructure grows, maintaining these copies becomes difficult.

A change to a shared resource may need to be repeated across multiple environments.

This creates opportunities for configuration drift, inconsistent infrastructure, and human error.

3. The Better Approach: Reusable Terraform Modules

Before introducing Terragrunt, understand Terraform modules.

A Terraform module allows you to write infrastructure code once and reuse it with different inputs.

Let’s create a reusable EC2 module.

Project structure:

terraform-project/
│
├── modules/
│   └── ec2/
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
│
└── environments/
    ├── dev/
    ├── staging/
    └── prod/

Step 1: Create the reusable module

Inside modules/ec2/main.tf:

resource "aws_instance" "app" {
  ami           = var.ami_id
  instance_type = var.instance_type

  tags = {
    Name        = var.instance_name
    Environment = var.environment
  }
}

Inside modules/ec2/variables.tf:

variable "ami_id" {
  type = string
}

variable "instance_type" {
  type = string
}

variable "instance_name" {
  type = string
}

variable "environment" {
  type = string
}

Inside modules/ec2/outputs.tf:

output "instance_id" {
  value = aws_instance.app.id
}

output "public_ip" {
  value = aws_instance.app.public_ip
}

You now have one reusable EC2 module.

Instead of maintaining three copies of the resource definition, you can reuse this module with different environment values.

4. Where Does Terragrunt Come In?

Terragrunt helps manage how these reusable modules are configured and deployed across environments.

Let’s organize the project like this:

terraform-project/
│
├── modules/
│   └── ec2/
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
│
└── live/
    ├── root.hcl
    │
    ├── dev/
    │   └── ec2/
    │       └── terragrunt.hcl
    │
    ├── staging/
    │   └── ec2/
    │       └── terragrunt.hcl
    │
    └── prod/
        └── ec2/
            └── terragrunt.hcl

The modules directory contains reusable infrastructure code.

The live directory contains environment-specific configuration.

This separation makes the architecture easier to maintain.

Step 2: Configure the root Terragrunt file

Create live/root.hcl:

remote_state {
  backend = "s3"

  config = {
    bucket = "company-terraform-state"
    key    = "${path_relative_to_include()}/terraform.tfstate"

    region         = "ap-south-1"
    encrypt        = true
    use_lockfile   = true
  }
}

This configures an S3 remote backend for Terraform state.

The key uses the environment’s relative directory path.

For example:

  • Dev: dev/ec2/terraform.tfstate

  • Staging: staging/ec2/terraform.tfstate

  • Production: prod/ec2/terraform.tfstate

This keeps environment state files separate.

Important: Create the S3 bucket before using this backend. Enable versioning, restrict access through IAM, and ensure your Terraform and Terragrunt versions support the backend configuration you’re using.

Step 3: Configure the Dev environment

Create live/dev/ec2/terragrunt.hcl:

include "root" {
  path = find_in_parent_folders("root.hcl")
}

terraform {
  source = "../../../modules/ec2"
}

inputs = {
  ami_id        = "ami-12345678"
  instance_type = "t3.micro"
  instance_name = "dev-app"
  environment   = "dev"
}

What is happening here?

  • include loads the shared root configuration.

  • terraform.source points to the reusable EC2 module.

  • inputs provides values for the Dev environment.

Step 4: Configure Staging

Create live/staging/ec2/terragrunt.hcl:

include "root" {
  path = find_in_parent_folders("root.hcl")
}

terraform {
  source = "../../../modules/ec2"
}

inputs = {
  ami_id        = "ami-12345678"
  instance_type = "t3.small"
  instance_name = "staging-app"
  environment   = "staging"
}

Step 5: Configure Production

Create live/prod/ec2/terragrunt.hcl:

include "root" {
  path = find_in_parent_folders("root.hcl")
}

terraform {
  source = "../../../modules/ec2"
}

inputs = {
  ami_id        = "ami-12345678"
  instance_type = "t3.large"
  instance_name = "prod-app"
  environment   = "prod"
}

Notice the important difference.

The Terraform module remains the same.

Only the environment-specific values change.

This means a common infrastructure improvement can be made in one reusable module instead of manually updating three separate copies.

5. How to Run the Configuration

Now let’s execute the Dev environment.

Navigate to:

cd live/dev/ec2

Initialize the configuration:

terragrunt init

This prepares the Terraform working directory and backend configuration.

Validate the configuration:

terragrunt validate

Review the infrastructure changes:

terragrunt plan

Apply the changes:

terragrunt apply

Repeat the process from the corresponding Staging or Production directory when you are ready to deploy those environments.

For Production, always review the plan carefully and use your organization’s approval process before applying changes.

6. What Happens When You Need to Change Something?

Imagine your company decides that every EC2 instance must have an additional security-related tag.

With duplicated Terraform configurations, you might need to edit multiple environment folders.

With the reusable module approach, you update the shared module once.

For example:

tags = {
  Name        = var.instance_name
  Environment = var.environment
  ManagedBy   = "Terraform"
}

All environments using that module can receive the updated configuration during their next planned deployment.

Terragrunt helps keep the environment-specific configuration organized while Terraform continues to manage the actual infrastructure.

One reusable module. Multiple environment configurations. Independent infrastructure states.

7. When Should You Use Terragrunt?

Terragrunt can be particularly useful when:

  • You manage Dev, Staging, and Production environments.

  • You reuse Terraform modules across multiple teams.

  • You have many infrastructure components.

  • You want consistent remote-state configuration.

  • Your infrastructure has dependencies between modules.

  • You want to coordinate Terraform operations across multiple environments or components.

For a very small Terraform project with only one environment, introducing Terragrunt may add unnecessary complexity.

Choose the architecture based on the actual scale and requirements of your infrastructure.

8. Important Production Best Practices

Before using this approach in a real environment, keep these practices in mind:

Separate state files: Never casually share one state file across independent environments.

Protect remote state: Use encryption, restricted IAM permissions, and versioning.

Avoid hardcoded secrets: Use appropriate secret-management services and secure runtime credentials.

Version reusable modules: Pin module sources to reviewed versions or Git references so environments don’t unexpectedly consume untested changes.

Review Terraform plans: Especially before Production deployments.

Test module changes: Validate changes in Dev and Staging before promoting them to Production.

Manage dependencies carefully: Use Terragrunt dependency blocks when one infrastructure component needs outputs from another.

Maintain environment isolation: Dev should not accidentally modify Production infrastructure.

9. The Final Architecture

Here is the basic relationship:

             TERRAFORM MODULES
                    |
          Reusable Infrastructure
                    |
          ---------------------
          |         |         |
         DEV     STAGING     PROD
          |         |         |
       Terragrunt Terragrunt Terragrunt
          |         |         |
       Terraform Terraform Terraform
          |         |         |
       AWS DEV   AWS STAGE  AWS PROD
          |         |         |
       Separate  Separate  Separate
       State     State     State

Terraform defines and manages the infrastructure.

Terragrunt supplies environment-specific configuration and helps coordinate how those Terraform modules are used.

That’s the key idea.

Final Thoughts

If you’re managing multiple Terraform environments by copying the same configuration repeatedly, it’s worth reconsidering your project structure.

Start with reusable Terraform modules.

Then introduce Terragrunt when you need better environment configuration, remote-state management, and coordination across infrastructure components.

You don’t need more configuration files. You need a maintainable infrastructure design.

Terraform builds the infrastructure. Terragrunt helps you manage Terraform at scale.

Learn the concepts. Build the structure. Deploy it in Dev. Validate it in Staging. Promote it safely to Production.

That’s how you move from writing Terraform files to managing infrastructure like a production engineer.


Connect With Me

I share practical insights on AWS, Terraform, Kubernetes, DevOps automation, production troubleshooting, and career development.

LinkedIn: https://www.linkedin.com/in/arvindverma021/

Medium: https://medium.com/@arvindverma021

Substack:

DevOps Learning Resources: https://topmate.io/arvindverma021/

Hashtags

#Terraform #Terragrunt #InfrastructureAsCode #IaC #AWS #DevOps #TerraformModules #CloudEngineering #InfrastructureAutomation #DevOpsEngineer #PlatformEngineering #SRE #CloudInfrastructure #TerraformState #DevOpsProjects #AWSDevOps #Automation #ProductionEngineering #TechLearning #CloudComputing

Arvind Verma is free today. But if you enjoyed this post, you can tell Arvind Verma that their writing is valuable by pledging a future subscription. You won't be charged unless they enable payments.

Pledge your support

© 2026 Arvind Verma
548 Market Street PMB 72296, San Francisco, CA 94104