Skip to main content

Loops and Conditionals: Dynamic Configurations with Count & For_Each

 Loops and Conditionals: Dynamic Configurations with Count & For_Each

Your complete guide to creating flexible, DRY Terraform configurations that adapt to your infrastructure needs—without repeating yourself.

📅 Published: Feb 2026
⏱️ Estimated Reading Time: 24 minutes
🏷️ Tags: Terraform Loops, Conditionals, Count, For_Each, Dynamic Blocks, Meta-Arguments


🎭 Introduction: The Problem of Repetition

The Copy-Paste Trap

Every Terraform user eventually faces this moment. You need three subnets. So you write:

hcl
resource "aws_subnet" "public_1" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.0.1.0/24"
  availability_zone       = "us-west-2a"
  map_public_ip_on_launch = true
}

resource "aws_subnet" "public_2" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.0.2.0/24"
  availability_zone       = "us-west-2b"
  map_public_ip_on_launch = true
}

resource "aws_subnet" "public_3" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.0.3.0/24"
  availability_zone       = "us-west-2c"
  map_public_ip_on_launch = true
}

This works. It's also a maintenance nightmare.

❌ Need to add a fourth subnet? Copy-paste-modify.

❌ Need to change the CIDR block pattern? Edit three places.

❌ Need to disable public IPs? Edit three places.

❌ Someone accidentally uses us-west-2a twice? Good luck catching that.

This is the copy-paste trap, and it's where most Terraform users start. Loops are how you escape.


What Loops Solve

Terraform provides two meta-arguments for creating multiple resources from a single definition:

Meta-argumentWorks WithBest For
countResources, modules, data sourcesSimple numbered lists
for_eachResources, modules, data sourcesMaps and sets of strings

These transform repetitive configurations into dynamic, data-driven ones:

hcl
# Instead of 3 nearly-identical subnet blocks...
resource "aws_subnet" "public" {
  count = 3
  
  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.0.${count.index + 1}.0/24"
  availability_zone       = data.aws_availability_zones.available.names[count.index]
  map_public_ip_on_launch = true
}

One block. Three resources. Zero repetition.


🔢 Count: The Simple Loop

How Count Works

count is a meta-argument that accepts a non-negative integer. When you add it to a resource, module, or data source, Terraform creates that many instances.

hcl
resource "aws_instance" "web" {
  count = 3
  
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  
  tags = {
    Name = "web-server-${count.index}"
  }
}

This creates three EC2 instances, accessible as:

  • aws_instance.web[0]

  • aws_instance.web[1]

  • aws_instance.web[2]

The count.index special variable starts at 0 and increments with each instance. This is your primary tool for making each instance different.


Count with Lists

The most common pattern: using count with length() to iterate over a list.

hcl
variable "subnet_cidrs" {
  description = "CIDR blocks for public subnets"
  type        = list(string)
  default     = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
}

resource "aws_subnet" "public" {
  count = length(var.subnet_cidrs)
  
  vpc_id     = aws_vpc.main.id
  cidr_block = var.subnet_cidrs[count.index]
  availability_zone = data.aws_availability_zones.available.names[count.index]
  
  tags = {
    Name = "public-subnet-${count.index + 1}"
  }
}

Benefits:

  • Add subnets by adding entries to var.subnet_cidrs

  • Remove subnets by removing entries

  • Never touch the resource block again


Count with Conditional Logic

Count can also be used for conditional resource creation—the most common pattern in Terraform.

hcl
variable "create_bastion" {
  description = "Whether to create a bastion host"
  type        = bool
  default     = false
}

resource "aws_instance" "bastion" {
  # Create 1 instance if true, 0 if false
  count = var.create_bastion ? 1 : 0
  
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  
  tags = {
    Name = "bastion-host"
  }
}

Then, to reference this conditionally created resource:

hcl
# Safe way to access conditional resource
output "bastion_ip" {
  value = var.create_bastion ? aws_instance.bastion[0].public_ip : null
}

# Or use try() to handle missing resources
output "bastion_ip_alt" {
  value = try(aws_instance.bastion[0].public_ip, null)
}

This pattern is used for:

  • Environment-specific resources (dev vs. prod)

  • Feature flags (enable_monitoring, enable_backups)

  • Optional components (bastion hosts, NAT gateways)

  • One-time setup resources (bootstrap jobs)


Count Limitations

⚠️ Count has a critical limitation: it works by position, not by value.

hcl
variable "users" {
  type    = list(string)
  default = ["alice", "bob", "charlie"]
}

resource "aws_iam_user" "this" {
  count = length(var.users)
  name  = var.users[count.index]
}

What happens when you remove "bob" from the list?

hcl
variable "users" {
  default = ["alice", "charlie"]  # Bob removed
}

Terraform sees:

  • Index 0: "alice" → matches existing user "alice" → update

  • Index 1: "charlie" → WAS "bob", NOW "charlie" → destroy bob, create charlie 😱

This is called index instability. When the list changes, Terraform renumbers the instances. Resources can be unexpectedly destroyed and recreated.

Solution: Use for_each when the order might change or when stable identities are important.


🔁 For_Each: The Stable Loop

How For_Each Works

for_each is a meta-argument that accepts a map or set of strings. It creates one instance per item, keyed by the map key or set value.

hcl
variable "users" {
  description = "Map of IAM users to create"
  type = map(object({
    groups = list(string)
    path   = optional(string, "/")
  }))
  default = {
    "alice" = {
      groups = ["developers", "ops"]
    }
    "bob" = {
      groups = ["developers"]
    }
    "charlie" = {
      groups = ["managers"]
      path   = "/leadership/"
    }
  }
}

resource "aws_iam_user" "this" {
  for_each = var.users
  
  name = each.key
  path = each.value.path
  
  tags = {
    Groups = join(",", each.value.groups)
  }
}

The magic of for_each: Each instance is identified by its key, not its position. Remove "bob" from the map, and Terraform knows exactly which user to delete. No renumbering, no accidental recreation.

Special variables:

  • each.key — The map key (or set value)

  • each.value — The map value (for maps; for sets, same as key)


For_Each with Sets

When you don't need complex values, a set of strings is perfect:

hcl
variable "allowed_accounts" {
  description = "AWS account IDs allowed to assume this role"
  type        = set(string)
  default     = ["123456789012", "210987654321"]
}

resource "aws_iam_role" "this" {
  name = "cross-account-role"
  
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          AWS = var.allowed_accounts  # This works directly!
        }
        Action = "sts:AssumeRole"
      }
    ]
  })
}

# But for resources that need per-item configuration:
resource "aws_iam_role_policy_attachment" "this" {
  for_each = var.allowed_accounts
  
  role       = aws_iam_role.this.name
  policy_arn = "arn:aws:iam::${each.value}:policy/OrganizationAccountAccessRole"
}

For_Each with Maps of Objects

This is the most powerful pattern—full configuration for each instance:

hcl
variable "databases" {
  description = "Database configurations"
  type = map(object({
    engine         = string
    engine_version = string
    instance_class = string
    storage_gb     = number
    multi_az       = optional(bool, false)
    backup_days    = optional(number, 7)
  }))
  
  default = {
    "users-db" = {
      engine         = "postgres"
      engine_version = "14.7"
      instance_class = "db.t3.micro"
      storage_gb     = 100
      multi_az       = false
    }
    "products-db" = {
      engine         = "mysql"
      engine_version = "8.0"
      instance_class = "db.t3.small"
      storage_gb     = 200
      multi_az       = true
      backup_days    = 30
    }
    "analytics-db" = {
      engine         = "aurora-postgresql"
      engine_version = "13.6"
      instance_class = "db.r5.large"
      storage_gb     = 1000
      backup_days    = 14
    }
  }
}

resource "aws_db_instance" "this" {
  for_each = var.databases
  
  identifier = each.key
  engine     = each.value.engine
  engine_version = each.value.engine_version
  instance_class = each.value.instance_class
  allocated_storage = each.value.storage_gb
  multi_az = each.value.multi_az
  backup_retention_period = each.value.backup_days
  
  # ... other required arguments
}

This is infrastructure as configuration. The module user provides a declarative map of what they want; the module creates exactly that.


For_Each on Modules

One of the most powerful features in Terraform—iterating over entire modules:

hcl
# modules/team-environment/main.tf
variable "team_name" {
  type = string
}

variable "environment_config" {
  type = object({
    instance_type = string
    instance_count = number
    enable_monitoring = bool
  })
}

resource "aws_instance" "app" {
  count = var.environment_config.instance_count
  
  instance_type = var.environment_config.instance_type
  # ... other configuration
}

# root/main.tf
locals {
  teams = {
    "payments" = {
      instance_type = "t3.large"
      instance_count = 3
      enable_monitoring = true
    }
    "checkout" = {
      instance_type = "t3.medium"
      instance_count = 2
      enable_monitoring = false
    }
    "fraud" = {
      instance_type = "t3.xlarge"
      instance_count = 5
      enable_monitoring = true
    }
  }
}

module "team_environments" {
  source = "./modules/team-environment"
  
  for_each = local.teams
  
  team_name = each.key
  environment_config = each.value
}

output "all_instances" {
  value = {
    for team, module in module.team_environments : team => module.instance_ids
  }
}

This is how platform teams scale. One module definition, dozens of instantiations, hundreds of resources—all managed with clean, declarative configuration.


🆚 Count vs. For_Each: When to Use Which

Decision Matrix

ScenarioUse CountUse For_Each
Simple numbered list (0,1,2,3)✅ Perfect🤔 Possible but awkward
Stable order guaranteed✅ Good✅ Also good
Order may change❌ Danger!✅ Safe
Resources may be added/removed⚠️ Risky✅ Safe
Each instance needs unique config⚠️ Limited to count.index✅ Full configuration per key
Conditional resource (create if true)✅ count = condition ? 1 : 0⚠️ for_each = condition ? {name = {}} : {}
Zero instances needed✅ count = 0✅ for_each = {}

Rule of Thumb

Use count when:

  • You need a simple numeric range (0..N)

  • You're conditionally creating a single resource

  • The order is stable and you never remove items from the middle

Use for_each when:

  • You have a set of strings or a map

  • Resources may be added or removed

  • Each instance needs different configuration

  • You want stable, predictable resource addresses

When in doubt, choose for_each. It's more explicit, more stable, and handles changes gracefully.


🧩 Dynamic Blocks: Nested Configuration Loops

What Are Dynamic Blocks?

Dynamic blocks allow you to create multiple nested blocks within a resource. They're like for_each for arguments that are blocks, not entire resources.

hcl
resource "aws_security_group" "web" {
  name   = "web-sg"
  vpc_id = aws_vpc.main.id
  
  # Dynamic block for ingress rules
  dynamic "ingress" {
    for_each = var.ingress_rules
    
    content {
      description = ingress.value.description
      from_port   = ingress.value.port
      to_port     = ingress.value.port
      protocol    = ingress.value.protocol
      cidr_blocks = ingress.value.cidr_blocks
    }
  }
}

Without dynamic blocks, you'd need separate security group rule resources. With dynamic blocks, everything is contained within the security group definition.


Dynamic Block Syntax

hcl
dynamic "block_type" {
  for_each = COLLECTION  # Map or set to iterate over
  
  # Iterator variables (optional)
  iterator = NAME  # Defaults to block_type
  
  content {
    # Can reference:
    # - block_type.value (or iterator.value)
    # - block_type.key   (map key, or index for list)
  }
}

Example with explicit iterator:

hcl
dynamic "tag" {
  for_each = var.tags
  iterator = tag_item  # Instead of default "tag"
  
  content {
    key   = tag_item.key
    value = tag_item.value
  }
}

Real-World Dynamic Block Examples

1. Security group rules

hcl
variable "ingress_rules" {
  type = list(object({
    description = string
    port        = number
    protocol    = string
    cidr_blocks = list(string)
  }))
  default = [
    {
      description = "HTTP"
      port        = 80
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    },
    {
      description = "HTTPS"
      port        = 443
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    }
  ]
}

resource "aws_security_group" "web" {
  name   = "web-sg"
  vpc_id = aws_vpc.main.id
  
  dynamic "ingress" {
    for_each = var.ingress_rules
    
    content {
      description = ingress.value.description
      from_port   = ingress.value.port
      to_port     = ingress.value.port
      protocol    = ingress.value.protocol
      cidr_blocks = ingress.value.cidr_blocks
    }
  }
}

2. IAM policy statements

hcl
variable "s3_permissions" {
  type = map(object({
    actions   = list(string)
    resources = list(string)
    effect    = optional(string, "Allow")
  }))
}

resource "aws_iam_policy" "s3_access" {
  name   = "s3-access-policy"
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      for name, config in var.s3_permissions : {
        Sid       = name
        Effect    = config.effect
        Action    = config.actions
        Resource  = config.resources
      }
    ]
  })
}

3. EBS block devices

hcl
variable "additional_volumes" {
  type = list(object({
    device_name = string
    volume_size = number
    volume_type = string
    encrypted   = optional(bool, true)
  }))
  default = []
}

resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t2.micro"
  
  dynamic "ebs_block_device" {
    for_each = var.additional_volumes
    
    content {
      device_name = ebs_block_device.value.device_name
      volume_size = ebs_block_device.value.volume_size
      volume_type = ebs_block_device.value.volume_type
      encrypted   = ebs_block_device.value.encrypted
    }
  }
}

4. CloudWatch alarm dimensions

hcl
variable "alarm_dimensions" {
  type = map(string)
  default = {
    Environment = "production"
    Service     = "web"
  }
}

resource "aws_cloudwatch_metric_alarm" "cpu_high" {
  alarm_name          = "cpu-utilization-high"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = 2
  metric_name         = "CPUUtilization"
  namespace           = "AWS/EC2"
  period              = 60
  statistic           = "Average"
  threshold           = 80
  
  dynamic "dimension" {
    for_each = var.alarm_dimensions
    
    content {
      name  = dimension.key
      value = dimension.value
    }
  }
}

Dynamic Block Limitations

⚠️ Dynamic blocks cannot be used everywhere. They only work in locations where Terraform expects a specific block type, not for arbitrary nesting.

❌ Cannot use dynamic blocks for:

  • Resource-level meta-arguments (countfor_eachprovider)

  • Module blocks

  • Provisioner blocks

  • Nested dynamic blocks (no nesting)

✅ Can use dynamic blocks for:

  • ingress/egress in aws_security_group

  • dimension in aws_cloudwatch_metric_alarm

  • tag in most AWS resources

  • logging in aws_s3_bucket

  • lifecycle_rule in aws_s3_bucket

  • Any block that repeats within a single resource


🔄 For Expressions: Loops in Local Values

What Are For Expressions?

For expressions create new collections from existing collections. They're used in locals and variable definitions, not directly in resources.

hcl
locals {
  users = ["alice", "bob", "charlie"]
  
  # Transform list → list
  uppercase_users = [for name in local.users : upper(name)]
  # Result: ["ALICE", "BOB", "CHARLIE"]
  
  # Transform list → map
  user_map = { for name in local.users : name => upper(name) }
  # Result: { alice = "ALICE", bob = "BOB", charlie = "CHARLIE" }
  
  # Transform with index
  users_with_index = [for i, name in local.users : "${i + 1}: ${name}"]
  # Result: ["1: alice", "2: bob", "3: charlie"]
}

Filtering with For Expressions

For expressions can include if clauses to filter collections:

hcl
locals {
  instances = [
    { name = "web-1", type = "t3.micro",  environment = "dev" },
    { name = "web-2", type = "t3.small",  environment = "prod" },
    { name = "db-1",  type = "t3.medium", environment = "prod" },
    { name = "web-3", type = "t3.micro",  environment = "dev" },
  ]
  
  # Filter: only prod instances
  prod_instances = [
    for instance in local.instances : instance
    if instance.environment == "prod"
  ]
  # Result: [web-2, db-1]
  
  # Filter and transform
  prod_instance_names = [
    for instance in local.instances : instance.name
    if instance.environment == "prod"
  ]
  # Result: ["web-2", "db-1"]
  
  # Filter into map
  prod_instance_map = {
    for instance in local.instances : instance.name => instance.type
    if instance.environment == "prod"
  }
  # Result: { "web-2" = "t3.small", "db-1" = "t3.medium" }
}

Advanced For Expression Patterns

1. Flattening nested structures:

hcl
locals {
  teams = {
    "platform" = ["alice", "bob"]
    "data"     = ["charlie", "diana"]
    "product"  = ["eve", "frank"]
  }
  
  # Flatten to list of all users
  all_users = flatten([
    for team, members in local.teams : [
      for member in members : {
        team   = team
        member = member
      }
    ]
  ])
  # Result: [{team="platform",member="alice"}, {team="platform",member="bob"}, ...]
}

2. Grouping resources:

hcl
locals {
  subnets = [
    { az = "us-west-2a", type = "public",  cidr = "10.0.1.0/24" },
    { az = "us-west-2b", type = "public",  cidr = "10.0.2.0/24" },
    { az = "us-west-2a", type = "private", cidr = "10.0.10.0/24" },
    { az = "us-west-2b", type = "private", cidr = "10.0.20.0/24" },
  ]
  
  # Group by availability zone
  subnets_by_az = {
    for subnet in local.subnets : subnet.az => subnet...
  }
  # Result: 
  # us-west-2a = [public, private]
  # us-west-2b = [public, private]
  
  # Group by type
  subnets_by_type = {
    for subnet in local.subnets : subnet.type => subnet...
  }
  # Result:
  # public  = [az-a, az-b]
  # private = [az-a, az-b]
}

3. Creating lookup maps:

hcl
locals {
  # Create a map from subnet CIDR to subnet object
  subnet_lookup = {
    for subnet in aws_subnet.public : subnet.cidr_block => {
      id = subnet.id
      az = subnet.availability_zone
    }
  }
}

# Then use it elsewhere
resource "aws_instance" "web" {
  subnet_id = local.subnet_lookup["10.0.1.0/24"].id
  # ...
}

🎯 Conditional Patterns Beyond Count

Conditional with For_Each

While count is simpler for single-resource conditions, for_each can also do conditionals:

hcl
# Using count (simpler)
resource "aws_instance" "bastion" {
  count = var.create_bastion ? 1 : 0
  # ...
}

# Using for_each (more consistent if you're already using for_each)
resource "aws_instance" "bastion" {
  for_each = var.create_bastion ? { default = {} } : {}
  
  # ... (each.value is empty object)
}

When to use which:

  • Count: When you only need conditional creation

  • For_each: When you need conditional creation AND you're already using for_each elsewhere for consistency


Conditional Expressions in Resources

You can use conditional logic inside resource arguments without loops:

hcl
resource "aws_db_instance" "main" {
  engine         = "postgres"
  engine_version = "14.7"
  instance_class = var.environment == "prod" ? "db.r5.large" : "db.t3.micro"
  multi_az       = var.environment == "prod" ? true : false
  
  # Conditionally set backup retention
  backup_retention_period = var.environment == "prod" ? 30 : 7
  
  # Conditionally enable deletion protection
  deletion_protection = var.environment == "prod"
  
  # Conditionally set parameter group
  parameter_group_name = var.environment == "prod" ? aws_db_parameter_group.prod.name : aws_db_parameter_group.dev.name
}

Conditional with Lookup Maps

For complex conditional logic, a lookup map is often clearer than nested ternaries:

hcl
locals {
  instance_types = {
    dev     = "t2.micro"
    staging = "t3.small"
    prod    = "t3.large"
  }
  
  instance_counts = {
    dev     = 1
    staging = 2
    prod    = 3
  }
  
  enable_monitoring = {
    dev     = false
    staging = true
    prod    = true
  }
}

resource "aws_instance" "web" {
  count = local.instance_counts[var.environment]
  
  instance_type = local.instance_types[var.environment]
  monitoring    = local.enable_monitoring[var.environment]
}

This is much more maintainable than:

hcl
instance_type = var.environment == "dev" ? "t2.micro" : var.environment == "staging" ? "t3.small" : "t3.large"

🧪 Practice Exercises

Exercise 1: Convert Count to For_Each

Task: Convert this configuration from count to for_each to prevent index instability.

hcl
variable "buckets" {
  description = "List of S3 bucket names"
  type        = list(string)
  default     = ["logs", "backups", "data"]
}

resource "aws_s3_bucket" "this" {
  count = length(var.buckets)
  
  bucket = var.buckets[count.index]
  
  tags = {
    Name = var.buckets[count.index]
  }
}

Solution:

hcl
variable "buckets" {
  description = "List of S3 bucket names"
  type        = list(string)
  default     = ["logs", "backups", "data"]
}

# Convert list to set for for_each
resource "aws_s3_bucket" "this" {
  for_each = toset(var.buckets)
  
  bucket = each.key
  
  tags = {
    Name = each.key
  }
}

Now when you remove "backups" from the list, Terraform knows exactly which bucket to delete—no accidental renumbering!


Exercise 2: Complex Dynamic Block

Task: Create a security group module that accepts a flexible list of ingress rules, including rules that reference other security groups.

hcl
variable "security_group_rules" {
  description = "Ingress rules for security group"
  type = list(object({
    description     = string
    from_port       = number
    to_port         = number
    protocol        = string
    cidr_blocks     = optional(list(string), [])
    security_groups = optional(list(string), [])
  }))
}

# Your solution here

Solution:

hcl
variable "security_group_rules" {
  description = "Ingress rules for security group"
  type = list(object({
    description     = string
    from_port       = number
    to_port         = number
    protocol        = string
    cidr_blocks     = optional(list(string), [])
    security_groups = optional(list(string), [])
  }))
}

resource "aws_security_group" "this" {
  name   = var.name
  vpc_id = var.vpc_id
  
  dynamic "ingress" {
    for_each = var.security_group_rules
    
    content {
      description = ingress.value.description
      from_port   = ingress.value.from_port
      to_port     = ingress.value.to_port
      protocol    = ingress.value.protocol
      cidr_blocks = ingress.value.cidr_blocks
      
      # Only include security_groups if provided
      security_groups = length(ingress.value.security_groups) > 0 ? 
        ingress.value.security_groups : null
    }
  }
}

Exercise 3: Multi-Environment Configuration

Task: Create a reusable EKS cluster module that can be instantiated multiple times with different configurations per team/environment.

hcl
# Solution:

# modules/eks-cluster/variables.tf
variable "cluster_config" {
  description = "EKS cluster configuration"
  type = object({
    name               = string
    environment        = string
    kubernetes_version = string
    node_groups = map(object({
      instance_types = list(string)
      min_size      = number
      max_size      = number
      desired_size  = number
    }))
  })
}

# modules/eks-cluster/main.tf
resource "aws_eks_cluster" "this" {
  name     = "${var.cluster_config.name}-${var.cluster_config.environment}"
  role_arn = aws_iam_role.cluster.arn
  version  = var.cluster_config.kubernetes_version
  
  vpc_config {
    subnet_ids = var.subnet_ids
  }
}

resource "aws_eks_node_group" "this" {
  for_each = var.cluster_config.node_groups
  
  cluster_name    = aws_eks_cluster.this.name
  node_group_name = each.key
  node_role_arn   = aws_iam_role.node_group.arn
  subnet_ids      = var.subnet_ids
  
  instance_types = each.value.instance_types
  
  scaling_config {
    min_size     = each.value.min_size
    max_size     = each.value.max_size
    desired_size = each.value.desired_size
  }
  
  depends_on = [
    aws_eks_cluster.this,
    aws_iam_role_policy_attachment.node_group
  ]
}

# root/main.tf
locals {
  clusters = {
    "platform-dev" = {
      environment = "dev"
      kubernetes_version = "1.28"
      node_groups = {
        "general" = {
          instance_types = ["t3.medium"]
          min_size      = 1
          max_size      = 3
          desired_size  = 2
        }
        "cpu-heavy" = {
          instance_types = ["c5.large"]
          min_size      = 0
          max_size      = 5
          desired_size  = 0
        }
      }
    }
    "platform-prod" = {
      environment = "prod"
      kubernetes_version = "1.28"
      node_groups = {
        "general" = {
          instance_types = ["t3.large", "t3.xlarge"]
          min_size      = 3
          max_size      = 10
          desired_size  = 5
        }
        "cpu-heavy" = {
          instance_types = ["c5.xlarge"]
          min_size      = 1
          max_size      = 20
          desired_size  = 2
        }
        "memory-heavy" = {
          instance_types = ["r5.large"]
          min_size      = 1
          max_size      = 10
          desired_size  = 2
        }
      }
    }
  }
}

module "eks_clusters" {
  source = "./modules/eks-cluster"
  
  for_each = local.clusters
  
  cluster_config = {
    name               = each.key
    environment        = each.value.environment
    kubernetes_version = each.value.kubernetes_version
    node_groups       = each.value.node_groups
  }
  
  subnet_ids = module.vpc.private_subnet_ids
}

📋 Loop and Conditional Patterns Cheat Sheet

PatternCodeUse Case
Count loopcount = 5Simple numbered resources
Count conditionalcount = var.create ? 1 : 0Optional resource
Count with listcount = length(var.list)Create per list item
For_each with setfor_each = toset(var.list)Create per unique string
For_each with mapfor_each = var.mapCreate with configuration
For_each conditionalfor_each = var.create ? {key={}} : {}Optional with for_each
Dynamic blockdynamic "ingress" { for_each = var.rules }Nested configuration blocks
For expression (list)[for v in var.list : upper(v)]Transform list → list
For expression (map){for k,v in var.map : k => upper(v)}Transform map → map
For with filter[for v in var.list : v if v != ""]Filter collections
Flattenflatten([[1,2], [3,4]])Flatten nested lists
Splat expressionvar.list[*].idExtract attribute from list of objects

🎓 Summary: Dynamic Configurations Are Professional Configurations

The difference between junior and senior Terraform usage isn't knowing more resources—it's knowing how to make resources dynamic.

Static ConfigurationDynamic Configuration
Subnets3 nearly-identical blockscount = 3 with CIDR calculation
Security rulesCopy-paste-modifydynamic "ingress" with variable rules
Multiple environmentsdev/, staging/, prod/ directoriesOne module, config maps per env
Multiple teamsCopy entire configurationsfor_each on module calls
Adding new resourceFind where to pasteAdd entry to configuration map

Master these patterns and you'll:

  • Write 80% less code

  • Make 100x fewer copy-paste errors

  • Enable self-service infrastructure for your teams

  • Sleep better knowing your configurations are consistent


🔗 Master Terraform Loops with Hands-on Labs

Theory is essential, but loops are best learned by doing. Practice these patterns in real scenarios until they become muscle memory.

👉 Practice count, for_each, and dynamic blocks with guided exercises at:
https://devops.trainwithsky.com/

Our platform provides:

  • Real-time validation of your loop logic

  • Complex multi-resource challenges

  • Dynamic block configuration exercises

  • Module iteration with for_each

  • Performance optimization for large counts


Frequently Asked Questions

Q: Why can't I use count.index in for_each?

A: for_each uses map keys or set values as identifiers, not positions. If you need a numeric index, you probably want count. If you need both index and stable identity, use:

hcl
for_each = { for i, v in var.list : i => v }

Q: Can I use count and for_each together?

A: No. A resource or module block cannot have both count and for_each. Choose one.

Q: How many resources can I create with loops?

A: Terraform has no hard limit, but practically, keep each module under a few hundred resources. Beyond that, performance degrades and API rate limits become an issue.

Q: What's the performance impact of loops?

A: Each instance is a separate resource in Terraform's graph. Planning time scales roughly linearly with the number of resources. For very large counts (>1000), consider splitting into multiple configurations.

Q: Can I loop over a list of maps in a dynamic block?

A: Yes! The for_each in a dynamic block accepts any collection type. For a list of maps:

hcl
dynamic "block" {
  for_each = var.list_of_maps
  
  content {
    attr1 = block.value.key1
    attr2 = block.value.key2
  }
}

Q: How do I conditionally include a dynamic block?

A: Filter the collection:

hcl
dynamic "ingress" {
  for_each = [
    for rule in var.rules : rule
    if rule.enabled
  ]
  
  content {
    # ...
  }
}

Q: Why does terraform plan show that my for_each resources will be destroyed and recreated when I change an unrelated value?

A: If you're using a computed value (like a resource attribute) as the for_each key, any change to that attribute will force recreation. Use static values (variables, locals, literals) as keys when possible.


Still confused about when to use count vs. for_each? Stuck on a dynamic block? Share your configuration in the comments—our community is here to help! 💬

Comments

Popular posts from this blog

Introduction to Terraform – The Future of Infrastructure as Code

  Introduction to Terraform – The Future of Infrastructure as Code In today’s fast-paced DevOps world, managing infrastructure manually is outdated . This is where Terraform comes in—a powerful Infrastructure as Code (IaC) tool that allows you to define, provision, and manage cloud infrastructure efficiently . Whether you're working with AWS, Azure, Google Cloud, or on-premises servers , Terraform provides a declarative, automation-first approach to infrastructure deployment. Shape Your Future with AI & Infinite Knowledge...!! Read In-Depth Tech & Self-Improvement Blogs http://www.skyinfinitetech.com Watch Life-Changing Videos on YouTube https://www.youtube.com/@SkyInfinite-Learning Transform Your Skills, Business & Productivity – Join Us Today! In today’s digital-first world, agility and automation are no longer optional—they’re essential. Companies across the globe are rapidly shifting their operations to the cloud to keep up with the pace of innovatio...

📊 Monitoring & Logging in Kubernetes – Tools like Prometheus, Grafana, and Fluentd

  Monitoring & Logging in Kubernetes – Tools like Prometheus, Grafana, and Fluentd Monitoring and logging are essential for maintaining a healthy and well-performing Kubernetes cluster. In this guide, we’ll cover why monitoring is important, key monitoring tools like Prometheus and Grafana, and logging tools like Fluentd to help you gain visibility into your cluster’s performance and logs. Shape Your Future with AI & Infinite Knowledge...!! Want to Generate Text-to-Voice, Images & Videos? http://www.ai.skyinfinitetech.com Read In-Depth Tech & Self-Improvement Blogs http://www.skyinfinitetech.com Watch Life-Changing Videos on YouTube https://www.youtube.com/@SkyInfinite-Learning Transform Your Skills, Business & Productivity – Join Us Today! 🚀 Introduction In today’s fast-paced cloud-native environment, Kubernetes has emerged as the de-facto container orchestration platform. But deploying and managing applications in Kubernetes is just half the ba...

🔒 Kubernetes Security – RBAC, Network Policies, and Secrets Management

  Kubernetes Security – RBAC, Network Policies, and Secrets Management Security is a critical aspect of managing Kubernetes clusters. In this guide, we'll cover essential security mechanisms like Role-Based Access Control (RBAC) , Network Policies , and Secrets Management to help you secure your Kubernetes environment effectively. Shape Your Future with AI & Infinite Knowledge...!! Want to Generate Text-to-Voice, Images & Videos? http://www.ai.skyinfinitetech.com Read In-Depth Tech & Self-Improvement Blogs http://www.skyinfinitetech.com Watch Life-Changing Videos on YouTube https://www.youtube.com/@SkyInfinite-Learning Transform Your Skills, Business & Productivity – Join Us Today! 🚀 Introduction: Why Kubernetes Security Is Non-Negotiable As Kubernetes becomes the backbone of modern cloud-native infrastructure, security is no longer optional—it’s mission-critical . With multiple moving parts like containers, pods, services, nodes, and more, Kuberne...