Still Copying Terraform Configurations Across Environments? There’s a Better Way with Terragrunt
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.tfYou 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.hclThe 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.tfstateStaging:
staging/ec2/terraform.tfstateProduction:
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?
includeloads the shared root configuration.terraform.sourcepoints to the reusable EC2 module.inputsprovides 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/ec2Initialize the configuration:
terragrunt initThis prepares the Terraform working directory and backend configuration.
Validate the configuration:
terragrunt validateReview the infrastructure changes:
terragrunt planApply the changes:
terragrunt applyRepeat 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 StateTerraform 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.
© 2026 Arvind Verma
548 Market Street PMB 72296, San Francisco, CA 94104