Mastering Infrastructure as Code: Terraform and Terragrunt for Multi-Cloud
Mastering Infrastructure as Code requires more than just Terraform. Learn how to use Terragrunt to simplify multi-cloud management and automate your DevOps workflows.
Managing infrastructure across AWS, GCP, and Azure using raw Terraform usually leads to one thing: a massive, unmaintainable mess. I’ve spent years cleaning up "copy-paste" modules where a single change in a network security group required updating twenty different directories. If you're tired of repeating yourself, it’s time to talk about Terragrunt.
Why Terraform Isn't Enough for Multi-Cloud
Terraform is excellent for provisioning, but it isn’t built to handle the complexity of multi-cloud environments at scale. When you manage multiple environments—dev, staging, production—across different cloud providers, you quickly hit the "DRY" (Don't Repeat Yourself) wall.
Standard Terraform forces you to duplicate backend configurations, provider blocks, and variable definitions. That’s where Terragrunt comes in. It’s a thin wrapper that provides extra tools for keeping your configurations dry, managing remote state, and orchestrating multiple modules.
Getting Started with Terragrunt
First, ensure you have the required tools installed. I’m currently using:
- Terraform v1.5.0
- Terragrunt v0.50.0
To start, move your backend configuration out of your main.tf and into a root terragrunt.hcl file. This is the magic of Terragrunt. You define the backend once, and every child module inherits it.
HCL# Root terragrunt.hcl remote_state { backend = "s3" generate = { path = "backend.tf" if_exists = "overwrite_terragrunt" } config = { bucket = "my-terraform-state-bucket" key = "${path_relative_to_include()}/terraform.tfstate" region = "us-east-1" encrypt = true dynamodb_table = "terraform-lock-table" } }
By using ${path_relative_to_include()}, Terragrunt automatically creates a unique state file path for every directory, preventing state collisions across your multi-cloud setup.
Solving Multi-Cloud Complexity
When you're dealing with Infrastructure as Code across providers, the biggest headache is variable propagation. You need to pass outputs from an AWS VPC to a GCP VPN gateway.
Terragrunt allows you to define dependencies between modules using the dependency block. This ensures that the infrastructure is applied in the correct order, and you can pull outputs directly from one module into another.
HCL# modules/gcp-vpn/terragrunt.hcl dependency "aws-vpc" { config_path = "../aws-vpc" } inputs = { remote_vpc_cidr = dependency.aws-vpc.outputs.vpc_cidr }
This approach makes DevOps automation feel like actual software engineering. You aren't just writing static files; you're building a dependency graph that the CLI resolves for you at runtime.
Managing Environments the Right Way
I’ve seen too many teams use git branches to manage staging vs. production. Don't do that. It leads to configuration drift that's impossible to debug.
Instead, use a folder structure that mirrors your architecture:
TEXTinfrastructure/ ├── terragrunt.hcl (root) ├── prod/ │ ├── aws/ │ │ └── vpc/terragrunt.hcl │ └── gcp/ │ └── k8s/terragrunt.hcl └── dev/ └── aws/ └── vpc/terragrunt.hcl
By keeping your code this way, you can run terragrunt apply-all from the prod/ directory to deploy your entire stack, or navigate to a specific folder to make a targeted change. It’s clean, predictable, and—most importantly—auditable.
Hard-Won Lessons from Production
- Keep modules versioned: Never point to local module paths in production. Use Git tags (e.g.,
source = "git::git@github.com:org/repo.git//modules/vpc?ref=v1.2.0"). This prevents a change in one environment from accidentally breaking another. - Use Terragrunt for partial runs: If you have 50 modules, don't run them all at once. Use the
--terragrunt-include-dirflag to target specific changes. - Automate the plan: If you aren't running
terragrunt planin your CI/CD pipeline, you’re flying blind. Make sure your pipeline fails if the plan isn't clean.
Final Thoughts
Multi-cloud management is inherently messy, but using Terraform combined with Terragrunt provides the abstraction layer necessary to keep your sanity. It forces you to think about your infrastructure as a coherent system rather than a collection of disparate shell scripts.
Stop repeating your configurations. Start treating your infrastructure as code that’s actually maintainable. Your future self—and your team—will thank you during the next midnight production incident.