· Infrastructure  · 2 min read

Infrastructure as Code with Terraform and Terragrunt

Stop clicking through cloud consoles. Here's how I use Terraform and Terragrunt to manage scalable, cost-optimized AWS infrastructure.

Stop clicking through cloud consoles. Here's how I use Terraform and Terragrunt to manage scalable, cost-optimized AWS infrastructure.

Why IaC Is Non-Negotiable

Clicking through cloud consoles is error-prone, impossible to review, and doesn’t scale. Every infrastructure change I’ve made in production for the past few years has gone through code — specifically Terraform and Terragrunt. Here’s what I’ve learned.

Terraform Basics

Terraform by HashiCorp is cloud-agnostic and declarative. Here’s a typical AWS setup I use:

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "eu-west-1"
}

Real-World Example: EKS Cluster

module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "~> 20.0"

  cluster_name    = "production"
  cluster_version = "1.29"

  vpc_id     = module.vpc.vpc_id
  subnet_ids = module.vpc.private_subnets

  eks_managed_node_groups = {
    default = {
      min_size     = 2
      max_size     = 10
      desired_size = 3
      instance_types = ["t3.medium"]
    }
  }

  tags = {
    Environment = "production"
    ManagedBy   = "terraform"
  }
}

Why Terragrunt?

Terraform alone struggles with DRY configuration across multiple environments. Terragrunt solves this:

infrastructure/
  terragrunt.hcl          # root config (remote state, provider)
  prod/
    eks/terragrunt.hcl
    rds/terragrunt.hcl
  staging/
    eks/terragrunt.hcl
    rds/terragrunt.hcl

Root terragrunt.hcl:

remote_state {
  backend = "s3"
  config = {
    bucket = "my-terraform-state"
    key    = "${path_relative_to_include()}/terraform.tfstate"
    region = "eu-west-1"
    encrypt = true
    dynamodb_table = "terraform-locks"
  }
}

Each environment’s terragrunt.hcl just includes the root and overrides what’s different. No copy-paste between environments.

The Core Workflow

terraform init    # Download providers and modules
terraform plan    # Preview changes — always review this
terraform apply   # Apply changes
terraform destroy # Tear down (use with caution in prod!)

With Terragrunt across environments:

terragrunt run-all plan   # Plan all modules
terragrunt run-all apply  # Apply all modules in dependency order

Cost Optimization Tips

From managing multi-service AWS infrastructure, here are patterns that save real money:

  • Use Spot instances for non-critical workloads (EKS node groups)
  • Karpenter for right-sized node provisioning
  • S3 lifecycle policies to move old data to cheaper storage tiers
  • CloudFront in front of everything to reduce origin load
  • RDS reserved instances for predictable database workloads

Conclusion

Terraform + Terragrunt is the combination I reach for on every new infrastructure project. The plan/apply cycle gives you confidence, the state file gives you auditability, and Terragrunt keeps your config DRY across environments. Once you go IaC, manual provisioning feels like a step back.

Back to Blog

Related Posts

View All Posts »
GitOps on Kubernetes with ArgoCD

GitOps on Kubernetes with ArgoCD

ArgoCD changed how I think about deployments. Here's how to set up GitOps for your Kubernetes workloads — and why you won't go back to manual kubectl applies.