Infrastructure at Scale: How Enterprises Use Terraform for_each for Complex Apps

Business Pain: Deploying a modern microservices application involves coordinating many cloud services, which is error-prone and slow when done manually.

for_each terraform

Terraform Solutions:

The for_each and count Meta-Arguments:

Concept: Dynamically creating multiple similar resources based on a map or list.

Business Value:

Efficiency: Create 10, 50, or 100 identical resources (like subnets, security groups, or IAM users) with a few lines of code.

Example: for_each = var.environments to create a full set of resources for each item in a list [“dev”, “staging”, “prod”].

Simple Explanation:

“Think of Your App Like a Restaurant Kitchen”

# A simple app is like a food truck - one of everything
resource "aws_instance" "server" {
  ami           = "ami-12345"
  instance_type = "t3.medium"
}

# A complex app is like a restaurant kitchen - multiple stations
locals {
  kitchen_stations = {
    "grill"    = { type = "c5.large", count = 2 }
    "fryer"    = { type = "t3.medium", count = 3 } 
    "prep"     = { type = "t3.small", count = 4 }
    "dessert"  = { type = "m5.large", count = 1 }
  }
}

# for_each creates each unique station
resource "aws_instance" "kitchen_station" {
  for_each = local.kitchen_stations
  
  instance_type = each.value.type
  count         = each.value.count  # Multiple identical stations
  
  tags = {
    Station = each.key
    Role    = "cooking"
  }
}

Beginner-Friendly Breakdown:

What Makes an App “Complex”?

  • Multiple services (frontend, backend, database, cache, queue)
  • Different environments (dev, staging, prod)
  • Various configurations per service
  • Scaling requirements

How for_each & count Help:

# Problem: Manual configuration for each service
# ❌ DON'T do this:
resource "aws_instance" "frontend" { /* config */ }
resource "aws_instance" "backend" { /* config */ }
resource "aws_instance" "database" { /* config */ }
resource "aws_instance" "cache" { /* config */ }

# ✅ DO this instead:
locals {
  services = {
    frontend = { instance_type = "t3.medium", port = 80 }
    backend  = { instance_type = "t3.large", port = 8080 }
    database = { instance_type = "r5.large", port = 5432 }
    cache    = { instance_type = "cache.t3.medium", port = 6379 }
  }
}

resource "aws_instance" "service" {
  for_each = local.services
  
  instance_type = each.value.instance_type
  user_data     = templatefile("${each.key}-init.sh", { port = each.value.port })
  
  tags = {
    Service = each.key
  }
}

Topic 1: “From Monolith to Microservices”

Post Content:
“When we split our monolith into microservices, our Terraform configs exploded from 200 to 2000+ lines. Here’s how for_each saved us:”

# Before: Monolithic configuration
resource "aws_instance" "monolith" {
  instance_type = "x2.16xlarge"  # One massive server
  # ... 100+ lines of config
}

# After: Microservices with for_each
locals {
  microservices = {
    user_service    = { instance_type = "t3.medium", team = "identity" }
    order_service   = { instance_type = "t3.large", team = "checkout" }
    payment_service = { instance_type = "t3.medium", team = "finance" }
    catalog_service = { instance_type = "t3.large", team = "product" }
    # 10+ more services...
  }
}

resource "aws_instance" "microservice" {
  for_each = local.microservices
  
  instance_type = each.value.instance_type
  
  tags = {
    Service    = each.key
    Team       = each.value.team
    CostCenter = "CC-${each.value.team}"
  }
}

# Results:
# ✅ 80% less code
# ✅ Consistent tagging
# ✅ Easy to add new services
# ✅ Team-specific resource tracking

Topic 2: “Multi-Environment Management”

Post Content:
“Managing dev, staging, and prod used to mean maintaining 3 separate Terraform configurations. Now we use one config with for_each:”

locals {
  environments = {
    dev = {
      instance_sizes = { web = "t3.small", api = "t3.small", db = "db.t3.small" }
      node_counts    = { web = 1, api = 1, db = 1 }
      auto_scaling   = false
    }
    staging = {
      instance_sizes = { web = "t3.medium", api = "t3.medium", db = "db.t3.medium" }
      node_counts    = { web = 2, api = 2, db = 1 }
      auto_scaling   = true
    }
    prod = {
      instance_sizes = { web = "t3.large", api = "t3.large", db = "db.r5.large" }
      node_counts    = { web = 3, api = 3, db = 2 }
      auto_scaling   = true
    }
  }
}

# Create complete environments with nested for_each
resource "aws_instance" "app_servers" {
  for_each = {
    for env, config in local.environments :
    "${env}-web" => {
      instance_type = config.instance_sizes.web
      count         = config.node_counts.web
      environment   = env
    }
  }
  
  instance_type = each.value.instance_type
  count         = each.value.count
  
  tags = {
    Environment = each.value.environment
    Role        = "web"
  }
}

Topic 3: “Database & Cache Layer Scaling”

Post Content:
“Our e-commerce platform needed multiple databases and cache clusters with different configurations. Here’s our for_each solution:”

locals {
  data_stores = {
    # Primary databases
    "postgres-primary" = {
      engine         = "postgres"
      instance_class = "db.r5.large"
      multi_az       = true
      backup_retention = 35
    }
    "mysql-sessions" = {
      engine         = "mysql"
      instance_class = "db.t3.medium"
      multi_az       = false
      backup_retention = 7
    }
    
    # Cache layers
    "redis-sessions" = {
      engine         = "redis"
      node_type      = "cache.t3.medium"
      num_cache_nodes = 2
    }
    "memcached-queries" = {
      engine         = "memcached"
      node_type      = "cache.t3.small"
      num_cache_nodes = 3
    }
  }
}

resource "aws_db_instance" "database" {
  for_each = {
    for k, v in local.data_stores : k => v
    if contains(["postgres", "mysql"], v.engine)
  }
  
  identifier     = "${var.app_name}-${each.key}"
  engine         = each.value.engine
  instance_class = each.value.instance_class
  multi_az       = each.value.multi_az
  backup_retention_period = each.value.backup_retention
  
  tags = {
    DataTier = "persistent"
    Service  = each.key
  }
}

resource "aws_elasticache_cluster" "cache" {
  for_each = {
    for k, v in local.data_stores : k => v
    if contains(["redis", "memcached"], v.engine)
  }
  
  cluster_id      = "${var.app_name}-${each.key}"
  engine          = each.value.engine
  node_type       = each.value.node_type
  num_cache_nodes = each.value.num_cache_nodes
  
  tags = {
    DataTier = "cache"
    Service  = each.key
  }
}

When to use Count vs for_Each

Use Count when:

  • You just need N identical resources (array-like).
  • You’re okay addressing them by index: aws_instance.web[0].
  • Your input is a number or an ordered list where order doesn’t matter long-term.

Use for_each when:

  • Each instance is tied to a unique key (set/map-like).
  • You want stable addressing by key: aws_instance.web["blue"].
  • You’ll add/remove specific items over time and want minimal churn (no index shifts).
  • You need to read fields from items (e.g., name, tags, az) and treat each differently.

Pitfalls to remember

  • Changing the order of a list with count can force recreations because indexes shift.
  • Changing a key in for_each is seen as “delete old key, create new key”.
  • for_each requires a set or map; convert lists with toset() or to maps with tomap() (or a for-expression).
  • Don’t use count and for_each on the same resource.

Detailed Comparison Table

Aspectcountfor_each
Data StructureListsMaps/Sets
Resource Identityresource[0]resource[1]resource["key"]
Modification SafetyRisky (index-based) Safe (key-based)
Unordered Data Not suitablePerfect
Code ReadabilityMediumHigh

    Business Use Cases

    Use Case 1: Environment Management (Use for_each)

    Business Problem: Standardizing multiple environments with different configurations

    # Business need: Different scaling for each environment
    locals {
      environments = {
        dev = {
          instance_type = "t3.small"
          min_nodes     = 1
          max_nodes     = 3
        }
        staging = {
          instance_type = "t3.medium" 
          min_nodes     = 2
          max_nodes     = 5
        }
        prod = {
          instance_type = "t3.large"
          min_nodes     = 3
          max_nodes     = 10
        }
      }
    }
    
    # for_each is perfect - each environment has unique config
    resource "aws_autoscaling_group" "app" {
      for_each = local.environments
      
      name_prefix = "app-${each.key}-"
      min_size    = each.value.min_nodes
      max_size    = each.value.max_nodes
      instance_type = each.value.instance_type
      
      tags = {
        Environment = each.key
        CostCenter  = each.key == "prod" ? "CC-001" : "CC-002"
      }
    }

    Why for_each? Each environment has different business requirements (cost, performance).

    Use Case 2: Multiple Identical Servers (Use count)

    Business Problem: Horizontal scaling of identical web servers

    # Business need: Load-balanced identical web servers
    variable "web_server_count" {
      description = "Number of identical web servers for load distribution"
      type        = number
      default     = 3
    }
    
    # count is perfect - all servers are identical
    resource "aws_instance" "web_server" {
      count = var.web_server_count  # Easy to scale up/down
      
      ami           = "ami-12345678"
      instance_type = "t3.medium"
      
      tags = {
        Name    = "web-server-${count.index}"
        Role    = "web"
        Tier    = "public"
      }
    }
    
    # Load balancer configuration
    resource "aws_lb_target_group_attachment" "web" {
      count = var.web_server_count
      
      target_group_arn = aws_lb_target_group.web.arn
      target_id        = aws_instance.web_server[count.index].id
    }

    Why count? All web servers are identical – perfect for horizontal scaling.

    Use Case 3: Regional Deployment (Use for_each)

    Business Problem: Multi-region deployment with region-specific settings

    # Business need: Compliance with data sovereignty laws
    locals {
      regions_config = {
        "us-east-1" = {
          cidr_block    = "10.1.0.0/16"
          compliance    = "hipaa"
          business_unit = "healthcare"
        }
        "eu-west-1" = {
          cidr_block    = "10.2.0.0/16" 
          compliance    = "gdpr"
          business_unit = "europe-retail"
        }
        "ap-southeast-1" = {
          cidr_block    = "10.3.0.0/16"
          compliance    = "local"
          business_unit = "asia-expansion"
        }
      }
    }
    
    # for_each is essential - each region has different business rules
    resource "aws_vpc" "regional" {
      for_each = local.regions_config
      
      cidr_block = each.value.cidr_block
      tags = {
        Region       = each.key
        Compliance   = each.value.compliance
        BusinessUnit = each.value.business_unit
      }
    }

    Why count? Simple on/off switch for resource creation.

    The SAFETY of for_each

    #  SAFE - Each resource has stable identity
    resource "aws_security_group" "app" {
      for_each = toset(["web", "app", "database"])
      
      name = "sg-${each.key}"
    }
    
    # Removing "app" from the set only destroys that specific resource
    # No unintended destruction/recreation of other resources

    Decision Framework for Business

    Choose count when:

    1. Identical Resources: All instances are the same
    2. Simple Scaling: Just need more of the same thing
    3. Feature Flags: Conditional resource creation
    4. Temporary Resources: Test environments, ephemeral workloads

    Choose for_each when:

    1. Unique Configurations: Each resource has different settings
    2. Stable Infrastructure: Production environments where stability matters
    3. Business Logic: Different departments, cost centers, compliance needs
    4. Map Data: You have natural keys for your resources

    Final Thoughts for Beginners

    Remember These 3 Rules:

    1. “If they’re identical, use count – Perfect for horizontal scaling
    2. “If they’re unique, use for_each – Essential for different configurations
    3. “When in doubt, start with for_each – More stable and predictable

    Business Value You’re Delivering:

    SkillBusiness Impact
    Using countFaster scaling, cost optimization
    Using for_eachConsistent environments, reduced errors
    Combining bothEnterprise-grade infrastructure automation

    Similar Posts

    Leave a Reply

    Your email address will not be published. Required fields are marked *