Terraform_terragrunt
Terragrunt
Section titled “Terragrunt”Overview
Section titled “Overview”Terragrunt is a thin wrapper for Terraform that provides extra tools for working with multiple Terraform modules. It helps keep your configuration DRY (Don’t Repeat Yourself) and manages remote state across multiple components.
Why Terragrunt?
Section titled “Why Terragrunt?”- DRY Terraform: Avoid repeating provider/backend configurations
- Multiple environments: Manage dev, staging, prod easily
- Remote state management: Built-in S3/GCS state handling
- Dependency management: Handle module dependencies
- Plan/apply locking: Prevent concurrent executions
Terragrunt vs Terraform
Section titled “Terragrunt vs Terraform”┌─────────────────────────────────────────────────────────────────┐│ Terragrunt Architecture ││ ││ ┌─────────────────────────────────────────────────────────┐ ││ │ terragrunt.hcl │ ││ │ │ ││ │ terraform { │ ││ │ source = "git::https://github.com/module.git" │ ││ │ } │ ││ │ │ ││ │ inputs = { │ ││ │ instance_type = "t3.micro" │ ││ │ } │ ││ └──────────────────────┬──────────────────────────────────┘ ││ │ ││ ▼ ││ ┌─────────────────────────────────────────────────────────┐ ││ │ Terraform │ ││ │ │ ││ │ Generates: main.tf, variables.tf, providers.tf │ ││ │ Runs: terraform init, plan, apply │ ││ └─────────────────────────────────────────────────────────┘ │└─────────────────────────────────────────────────────────────────┘Basic Terragrunt Configuration
Section titled “Basic Terragrunt Configuration”terragrunt.hcl
Section titled “terragrunt.hcl”terraform { source = "git::https://github.com/example/modules.git//ec2?ref=v1.0.0"}
inputs = { instance_type = "t3.micro" ami_id = "ami-0c55b159cbfafe1f0" tags = { Environment = "dev" ManagedBy = "terragrunt" }}Remote State Configuration
Section titled “Remote State Configuration”generate = { path = "backend.tf" if_exists = "overwrite_terragrunt" template = <<EOFterraform { backend "s3" { bucket = "my-terraform-state" key = "%s" region = "us-east-1" encrypt = true dynamodb_table = "terraform-locks" }}EOF}Multiple Environments
Section titled “Multiple Environments”├── environments/│ ├── dev/│ │ ├── terragrunt.hcl│ │ └── services/│ │ └── app/│ │ └── terragrunt.hcl│ ├── staging/│ │ └── ...│ └── prod/│ └── ...Environment Configuration
Section titled “Environment Configuration”locals { environment = "dev"}
inputs = { environment = local.environment}Dependency Configuration
Section titled “Dependency Configuration”dependencies { paths = ["../database", "../vpc"]}
inputs = { vpc_id = dependency.vpc.outputs.vpc_id subnet_ids = dependency.vpc.outputs.private_subnets db_host = dependency.database.outputs.db_host}Remote State Management
Section titled “Remote State Management”S3 Backend with Terragrunt
Section titled “S3 Backend with Terragrunt”remote_state { backend = "s3" config = { bucket = "my-terraform-state" key = "${path_relative_to_include()}/terraform.tfstate" region = "us-east-1" encrypt = true dynamodb_table = "terraform-locks" s3_bucket_tags = { Name = "Terraform State" Environment = "Production" } } generate = { path = "backend.tf" if_exists = "overwrite" }}GCS Backend
Section titled “GCS Backend”remote_state { backend = "gcs" config = { bucket = "my-terraform-state" prefix = "environments/prod" project = "my-project" }}Terragrunt Commands
Section titled “Terragrunt Commands”# Initializeterragrunt init
# Planterragrunt planterragrunt plan -out=plan.tfplan
# Applyterragrunt applyterragrunt apply plan.tfplan
# Destroyterragrunt destroy
# Run all in directoryterragrunt run-all planterragrunt run-all applyterragrunt run-all destroy
# Importterragrunt import aws_instance.example i-1234567890abcdef0
# Validateterragrunt validateterragrunt validate-all
# Outputterragrunt outputterragrunt output -json > outputs.jsonLocking and Concurrency
Section titled “Locking and Concurrency”# Prevent concurrent runslock = { enable = true}
# Configure retryretry { max_attempts = 3 sleep = "5s"}Using Terragrunt with Multiple Providers
Section titled “Using Terragrunt with Multiple Providers”# Generate provider configurationgenerate = { path = "providers.tf" if_exists = "overwrite_terragrunt" template = <<EOFprovider "aws" { region = "${var.aws_region}"
default_tags { tags = { Environment = "${var.environment}" ManagedBy = "Terragrunt" } }}
provider "azurerm" { features {} skip_provider_registration = true}
provider "google" { project = "${var.gcp_project}" region = "${var.gcp_region}"}EOF}Best Practices
Section titled “Best Practices”1. Use Terragrunt in All Environments
Section titled “1. Use Terragrunt in All Environments”# Always use --terragrunt-non-interactiveterragrunt plan --terragrunt-non-interactive2. Use Modules
Section titled “2. Use Modules”# Reuse tested modulesterraform { source = "git::https://github.com/terraform-aws-modules/terraform-aws-vpc.git?ref=v3.0.0"}3. Keep State Organized
Section titled “3. Keep State Organized”# Use descriptive state keyskey = "environments/${var.environment}/${path_relative_to_include()}/terraform.tfstate"4. Use Inputs for Variation
Section titled “4. Use Inputs for Variation”# Single module, different inputs per environmentinputs = { instance_type = var.environment == "prod" ? "t3.medium" : "t3.micro"}CI/CD Integration
Section titled “CI/CD Integration”GitHub Actions
Section titled “GitHub Actions”name: Terragrunt
on: [push, pull_request]
jobs: terragrunt: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3
- name: Setup Terragrunt uses: autero1/action-terragrunt@v1.0.0 with: terragrunt_version: 0.40.0
- name: Terragrunt Plan run: | terragrunt run-all plan --terragrunt-non-interactive env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}GitLab CI
Section titled “GitLab CI”stages: - validate - plan - apply
variables: TERRAGRUNT_VERSION: "0.40.0"
terragrunt-init: stage: validate image: name: alpine/terragrunt:${TERRAGRUNT_VERSION} entrypoint: [""] script: - terragrunt init - terragrunt validate
terragrunt-plan: stage: plan image: name: alpine/terragrunt:${TERRAGRUNT_VERSION} entrypoint: [""] script: - terragrunt run-all plan --terragrunt-non-interactive artifacts: paths: - plan.out
terragrunt-apply: stage: apply image: name: alpine/terragrunt:${TERRAGRUNT_VERSION} entrypoint: [""] script: - terragrunt run-all apply --terragrunt-non-interactive when: manualTerragrunt with Workspaces
Section titled “Terragrunt with Workspaces”# Automatically use workspace as environmentlocals { workspace = runterragru.workspace
environment_map = { "default" = "dev" "production" = "prod" }
environment = lookup(local.environment_map, local.workspace, local.workspace)}
inputs = { environment = local.environment}Summary
Section titled “Summary”Terragrunt is essential for:
- DRY configurations: Avoid repeating code
- Multi-environment management: Dev, staging, prod
- State management: Built-in remote state handling
- Dependencies: Handle module dependencies
- Team collaboration: Locking and coordination
Key concepts:
- terragrunt.hcl: Configuration file
- run-all: Execute across multiple modules
- generate: Generate Terraform code
- remote_state: Built-in state management
- dependencies: Module dependencies