Skip to main content
Terraform count and for_each: Create Multiple Resources with Examples

Terraform count and for_each: Create Multiple Resources with Examples

Key Takeaway

Learn how to use Terraform count and for_each to create multiple resources. Side-by-side comparison, practical examples, conditional creation

Table of Contents

Introduction

When you need to create multiple similar resources in Terraform — three subnets, five EC2 instances, or a security group per environment — you use count or for_each. Both create multiple resource instances, but they work differently and have different strengths.

Quick Comparison

Featurecountfor_each
InputNumberMap or set of strings
Referencecount.index (0, 1, 2…)each.key, each.value
Resource addressaws_instance.web[0]aws_instance.web["app"]
Removing middle itemShifts all subsequent indicesOnly removes that item
Best forIdentical resourcesResources with unique config

count: Create N Identical Resources

count takes a number and creates that many instances:

resource "aws_instance" "web" {
  count         = 3
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"

  tags = {
    Name = "web-${count.index}"
  }
}

This creates web-0, web-1, web-2.

Access count resources

# Single instance
output "first_ip" {
  value = aws_instance.web[0].public_ip
}

# All instances
output "all_ips" {
  value = aws_instance.web[*].public_ip
}

count with variables

variable "instance_count" {
  type    = number
  default = 3
}

resource "aws_instance" "web" {
  count         = var.instance_count
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"
}

Conditional creation with count

Use count = 0 or count = 1 to conditionally create resources:

variable "create_monitoring" {
  type    = bool
  default = true
}

resource "aws_cloudwatch_metric_alarm" "cpu" {
  count = var.create_monitoring ? 1 : 0

  alarm_name          = "high-cpu"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = 2
  metric_name         = "CPUUtilization"
  namespace           = "AWS/EC2"
  period              = 120
  statistic           = "Average"
  threshold           = 80
}

# Reference conditional resource (may not exist)
output "alarm_arn" {
  value = var.create_monitoring ? aws_cloudwatch_metric_alarm.cpu[0].arn : null
}

count with lists

Use count.index to index into a list:

variable "subnet_cidrs" {
  default = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
}

variable "availability_zones" {
  default = ["us-east-1a", "us-east-1b", "us-east-1c"]
}

resource "aws_subnet" "public" {
  count             = length(var.subnet_cidrs)
  vpc_id            = aws_vpc.main.id
  cidr_block        = var.subnet_cidrs[count.index]
  availability_zone = var.availability_zones[count.index]

  tags = {
    Name = "public-${count.index}"
  }
}

for_each: Create Resources From a Map or Set

for_each creates one instance per item in a map or set:

resource "aws_instance" "web" {
  for_each      = toset(["app", "api", "worker"])
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"

  tags = {
    Name = each.value
  }
}

This creates aws_instance.web["app"], aws_instance.web["api"], aws_instance.web["worker"].

for_each with a map

variable "instances" {
  default = {
    app = {
      instance_type = "t3.medium"
      disk_size     = 50
    }
    api = {
      instance_type = "t3.small"
      disk_size     = 30
    }
    worker = {
      instance_type = "t3.large"
      disk_size     = 100
    }
  }
}

resource "aws_instance" "web" {
  for_each      = var.instances
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = each.value.instance_type

  root_block_device {
    volume_size = each.value.disk_size
  }

  tags = {
    Name = each.key
    Role = each.key
  }
}

Access for_each resources

# Single instance
output "app_ip" {
  value = aws_instance.web["app"].public_ip
}

# All IPs as a map
output "all_ips" {
  value = { for k, v in aws_instance.web : k => v.public_ip }
}

for_each with a set of strings

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

resource "aws_iam_user" "team" {
  for_each = var.users
  name     = each.value
}

The Index Problem: Why for_each Is Safer

This is the biggest difference. With count, resources are identified by index. If you remove an item from the middle, everything shifts:

# Original: count = 3, creates [0], [1], [2]
# Remove the middle server:
# [0] stays, [1] becomes the old [2], old [2] is destroyed
# Terraform destroys and recreates resources!

With for_each, resources are identified by key. Removing an item only affects that item:

# Original: for_each with "app", "api", "worker"
# Remove "api":
# "app" stays, "worker" stays, only "api" is destroyed
# No unnecessary changes!

Rule of thumb: If resources might be added or removed independently, use for_each.

When to Use count vs for_each

Use count when:

  • Creating N identical copies (e.g., count = 3 replicas)
  • Conditional creation (count = var.enabled ? 1 : 0)
  • Resources are truly interchangeable (any can be destroyed/recreated)

Use for_each when:

  • Each resource has unique configuration
  • Resources are identified by name/key (subnets, users, environments)
  • Resources may be added or removed independently
  • You need stable resource addresses

Practical Decision Guide

Need N identical copies?           → count
Conditional resource?              → count (0 or 1)
Each resource has a unique name?   → for_each
Resources from a list of names?    → for_each (with toset())
Resources from a map of configs?   → for_each
Might remove items from the list?  → for_each (avoids index shifting)

Real-World Examples

Multi-Environment Setup

variable "environments" {
  default = {
    dev = {
      instance_type = "t3.micro"
      min_size      = 1
      max_size      = 2
    }
    staging = {
      instance_type = "t3.small"
      min_size      = 2
      max_size      = 4
    }
    production = {
      instance_type = "t3.large"
      min_size      = 3
      max_size      = 10
    }
  }
}

resource "aws_autoscaling_group" "app" {
  for_each         = var.environments
  name             = "asg-${each.key}"
  min_size         = each.value.min_size
  max_size         = each.value.max_size
  desired_capacity = each.value.min_size

  tag {
    key                 = "Environment"
    value               = each.key
    propagate_at_launch = true
  }
}

Security Group Rules

variable "ingress_rules" {
  default = {
    http = {
      port        = 80
      cidr_blocks = ["0.0.0.0/0"]
    }
    https = {
      port        = 443
      cidr_blocks = ["0.0.0.0/0"]
    }
    ssh = {
      port        = 22
      cidr_blocks = ["10.0.0.0/8"]
    }
  }
}

resource "aws_security_group_rule" "ingress" {
  for_each          = var.ingress_rules
  type              = "ingress"
  from_port         = each.value.port
  to_port           = each.value.port
  protocol          = "tcp"
  cidr_blocks       = each.value.cidr_blocks
  security_group_id = aws_security_group.web.id
  description       = each.key
}

Combining count and for_each

You can’t use both on the same resource, but you can use them in nested modules:

# Module uses count for replicas
module "service" {
  source   = "./modules/service"
  for_each = var.services

  name          = each.key
  instance_type = each.value.instance_type
  replicas      = each.value.replicas  # count inside module
}

Common Errors

“Invalid for_each argument”

The given "for_each" argument value is unsuitable: the "for_each" argument
must be a map, or set of strings, and you have provided a value of type list.

Fix: Convert list to set:

for_each = toset(var.my_list)

“Invalid count argument”

The "count" value depends on resource attributes that cannot be determined
until apply.

Fix: Use values known at plan time (variables, locals, data sources).

Hands-On Courses

Learn by doing with interactive courses on CopyPasteLearn:

Conclusion

Use count for simple numeric repetition and conditional creation. Use for_each when resources have unique identities and need stable addresses. When in doubt, prefer for_each — it’s safer when resources change over time because it avoids the index shifting problem that count has.

🚀

Level Up Your Terraform Skills

Hands-on courses, books, and resources from Luca Berton

Luca Berton
Written by

Luca Berton

DevOps Engineer, AWS Partner, Terraform expert, and author. Creator of Ansible Pilot, Terraform Pilot, and CopyPasteLearn.