This repository demonstrates the power of Terragrunt over plain Terraform using the DRY (Don't Repeat Yourself) principle.
With plain Terraform, you'd need to:
- Copy/paste backend configuration in every module
- Duplicate provider configuration across environments
- Manually manage dependencies between modules
- Repeat variable definitions for each environment
- Maintain separate tfvars files with lots of duplication
This demo showcases how Terragrunt eliminates repetition:
-
Auto-Generated Backend Infrastructure
- S3 buckets created automatically (per environment)
- DynamoDB tables created automatically (per environment)
- No manual AWS setup required
- Proper encryption and tagging applied
-
Centralized Configuration Maps (
root.hcl)- All environment-specific values defined in ONE place
- Environment automatically detected from path
- Values automatically selected based on environment
- Zero configuration needed in environment files!
-
Ultimate DRY Principle
- Environment files are EMPTY (just include statements)
- All configuration lives in root.hcl maps
- Change dev/staging/prod values in one place
- No duplication whatsoever
-
Automatic Dependency Management
- EC2 module automatically gets VPC outputs
- No manual state file references needed
- Dependencies declared cleanly in terragrunt.hcl
-
Module Reusability
- Same modules used across all environments
- No code duplication
- Easy to maintain and update
# In root.hcl - define once, use everywhere
vpc_cidr_map = {
dev = "10.10.0.0/16"
staging = "10.20.0.0/16"
prod = "10.30.0.0/16"
}
# Automatically derived based on path
vpc_cidr = local.vpc_cidr_map[local.environment].
βββ root.hcl # Root configuration (ALL config here!)
βββ modules/ # Reusable Terraform modules
β βββ vpc/ # VPC with subnets, IGW, route tables
β βββ ec2/ # EC2 instances with security groups
β βββ s3/ # S3 buckets with encryption
βββ environments/
βββ dev/
β βββ vpc/terragrunt.hcl # Just includes root.hcl
β βββ ec2/terragrunt.hcl # Just includes root.hcl
β βββ s3/terragrunt.hcl # Just includes root.hcl
βββ staging/
β βββ vpc/terragrunt.hcl # Just includes root.hcl
β βββ ec2/terragrunt.hcl # Just includes root.hcl
β βββ s3/terragrunt.hcl # Just includes root.hcl
βββ prod/
βββ vpc/terragrunt.hcl # Just includes root.hcl
βββ ec2/terragrunt.hcl # Just includes root.hcl
βββ s3/terragrunt.hcl # Just includes root.hcl
- Terragrunt installed
- Terraform installed
- AWS credentials configured
- AWS CLI installed
-
No manual setup required! Terragrunt automatically creates:
- S3 buckets for state storage (per environment)
- DynamoDB tables for state locking (per environment)
- Proper encryption and versioning
-
Initialize and deploy an environment:
# Initialize all modules in dev (creates backend automatically)
cd environments/dev
terragrunt run --all init
# Deploy all resources in dev
terragrunt run --all apply
# Or deploy individual modules
cd vpc
terragrunt apply
cd ../ec2
terragrunt apply
cd ../s3
terragrunt apply- Deploy all environments:
# Initialize and deploy dev
cd environments/dev
terragrunt run --all init
terragrunt run --all apply
# Initialize and deploy staging
cd ../staging
terragrunt run --all init
terragrunt run --all apply
# Initialize and deploy prod
cd ../prod
terragrunt run --all init
terragrunt run --all applyAll resources are configured to use AWS Free Tier:
- EC2: t2.micro instances (750 hours/month free)
- VPC: No charge for VPC, subnets, route tables
- S3: 5GB storage, 20,000 GET requests, 2,000 PUT requests free
- DynamoDB: 25GB storage, 25 read/write capacity units free
Note: You're responsible for monitoring your AWS usage. Set up billing alerts!
# root.hcl - All environment configs in one place
locals {
vpc_cidr_map = {
dev = "10.10.0.0/16"
staging = "10.20.0.0/16"
prod = "10.30.0.0/16"
}
instance_count_map = {
dev = 1
staging = 2
prod = 2
}
# Auto-derive based on environment
environment = regex(".*/environments/(?P<env>[^/]+)/.*", get_terragrunt_dir()).env
vpc_cidr = local.vpc_cidr_map[local.environment]
instance_count = local.instance_count_map[local.environment]
}
inputs = {
vpc_cidr = local.vpc_cidr
instance_count = local.instance_count
}# environments/dev/vpc/terragrunt.hcl - Just 2 blocks!
include "root" {
path = find_in_parent_folders("root.hcl")
}
terraform {
source = "../../../modules/vpc"
}
# Everything else inherited from root.hclremote_state {
backend = "s3"
config = {
bucket = "terragrunt-demo-state-${local.environment}-${get_aws_account_id()}"
dynamodb_table = "terragrunt-demo-locks-${local.environment}"
# Terragrunt automatically creates these resources!
}
}generate "provider" {
path = "provider.tf"
if_exists = "overwrite_terragrunt"
contents = <<EOF
provider "aws" {
region = "${local.aws_region}"
}
EOF
}dependency "vpc" {
config_path = "../vpc"
}
inputs = {
vpc_id = dependency.vpc.outputs.vpc_id
}bucket_name = "my-bucket-${get_aws_account_id()}"
environment = regex(".*/environments/(?P<env>[^/]+)/.*", get_terragrunt_dir()).env| Feature | Plain Terraform | With Terragrunt |
|---|---|---|
| Backend setup | Manual S3/DynamoDB creation | Auto-created by Terragrunt |
| Backend config | Repeated in every module | Defined once in root.hcl |
| Provider config | Repeated in every module | Generated automatically |
| Environment configs | Separate tfvars files with duplication | Centralized maps in root.hcl |
| Environment files | 50+ lines each with duplication | EMPTY (just includes!) |
| Dependencies | Manual remote state data sources | Automatic dependency blocks |
| Common tags | Repeated or complex locals | Defined once, applied everywhere |
| Changing env values | Edit 3+ files | Edit ONE map in root.hcl |
| Lines of config | ~500+ lines | ~150 lines (70% reduction!) |
To destroy all resources:
# Destroy specific environment (automatically handles dependencies)
cd environments/dev
terragrunt run --all destroy
# Or destroy individual modules (in reverse dependency order)
cd environments/dev/ec2
terragrunt destroy
cd ../s3
terragrunt destroy
cd ../vpc
terragrunt destroy# Initialize all modules
terragrunt run --all init
# Plan all modules
terragrunt run --all plan
# Apply all modules
terragrunt run --all apply
# Destroy all modules
terragrunt run --all destroy
# Validate all modules
terragrunt run --all validate
# Show dependency graph
terragrunt dag
# Run specific command on all modules
terragrunt run --all -- <terraform-command>After exploring this demo, you should understand:
- How Terragrunt eliminates code duplication
- How to structure multi-environment infrastructure
- How to manage dependencies between modules
- How to use inheritance and includes effectively
- Why Terragrunt makes Terraform more maintainable
Pro Tip: Compare the size of this Terragrunt setup with an equivalent plain Terraform setup. You'll find Terragrunt reduces configuration by 50-70% while adding powerful features!