MHRubel
HomeAboutProjectsSkillsExperienceBlogContact
MHRubel

Senior Software Engineer crafting high-performance web applications and SaaS platforms.

Navigation

  • Home
  • About
  • Projects
  • Skills
  • Experience
  • Blog
  • Contact

Get in Touch

Available for senior/lead roles and consulting.

bd.mhrubel@gmail.comHire Me

© 2026 Mahamudul Hasan Rubel. All rights reserved.

Built with using Next.js 16 & Tailwind v4

Back to Blog
Software EngineeringTechnologyJune 18, 20263 min read

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.

TerraformTerragruntInfrastructure as CodeMulti-cloudDevOpsAutomationCloud EngineeringLinuxServer

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:

TEXT
infrastructure/
├── 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

  1. 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.
  2. Use Terragrunt for partial runs: If you have 50 modules, don't run them all at once. Use the --terragrunt-include-dir flag to target specific changes.
  3. Automate the plan: If you aren't running terragrunt plan in 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.

Back to Blog

Similar Posts

Software EngineeringJune 19, 20263 min read

Terraform Infrastructure as Code Drift Detection and Remediation

Master Infrastructure as Code drift detection with Terraform and Driftctl. Learn how to automate remediation and keep your cloud environment synchronized.

Read more
TechnologyJune 19, 20263 min read

Kubernetes Cluster API: Automating Node Upgrades with CAPI

Master Kubernetes Cluster API for automated node upgrades. Learn how to leverage MachineHealthCheck for reliable, hands-off node lifecycle management today.

Read more
Software EngineeringJune 19, 20263 min read

Kubernetes Autoscaling: Karpenter vs Cluster Autoscaler Guide

Master Kubernetes autoscaling by comparing Cluster Autoscaler and Karpenter. Learn how to optimize node provisioning for efficient cloud infrastructure automation.

Read more