CoursesTerraformIntermediate

count, for_each & dynamic blocks

Build many resources from data.

Intermediate14 min · lesson 10 of 15

Real infrastructure is repetitive — three identical subnets, a security-group rule per port, one bucket per team. You do not copy-paste those; you generate them from data with the meta-arguments count and for_each, and the dynamic block. These turn a list or map into many resources, and choosing between count and for_each correctly is one of the highest-leverage Terraform skills.

main.tf
# count: N copies, indexed 0..N-1
resource "aws_instance" "web" {
count = 3
instance_type = "t3.micro"
tags = { Name = "web-${count.index}" } # web-0, web-1, web-2
}
# for_each: one resource per map/set entry, keyed by identity
resource "aws_iam_user" "team" {
for_each = toset(["alice", "bob", "carol"])
name = each.key # each.key / each.value
}

Why for_each usually beats count

count is positional: resources are tracked by index (web[0], web[1], web[2]). Remove the middle item from the list and everything after it shifts index — Terraform sees web[1] and web[2] as changed and may destroy and recreate them, even though you only meant to delete one. for_each keys resources by a stable identity (the map key), so removing one entry touches only that one. Prefer for_each whenever the collection can change; reserve count for a fixed number of truly identical copies, or a simple on/off toggle.

terminal
# count pitfall: delete "bob" from the middle of a list of 3
-/+ aws_instance.web[1] # was carol, now bob’s slot — destroyed & recreated
-/+ aws_instance.web[2] # shifted — churned needlessly
# for_each: delete "bob" from the map
- aws_iam_user.team["bob"] # only bob is removed; alice & carol untouched

dynamic blocks

Some resources have repeatable nested blocks — many ingress rules inside one security group. A dynamic block generates those nested blocks from a collection, the same idea as for_each but for blocks within a resource rather than whole resources. Useful, but do not reach for it prematurely: a couple of static ingress blocks are clearer than a dynamic one, so use it when the list is genuinely data-driven or long.

main.tf
resource "aws_security_group" "web" {
dynamic "ingress" {
for_each = var.allowed_ports # e.g. [80, 443]
content {
from_port = ingress.value
to_port = ingress.value
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
}
Switching count ↔ for_each forces churn
Because count indexes by position and for_each keys by identity, converting a resource from one to the other changes every instance’s state address — Terraform sees the old addresses gone and new ones added, and plans to destroy and recreate everything. When you must switch (usually count → for_each to fix the reindex problem), use moved blocks or terraform state mv to re-map the addresses so no infrastructure is actually destroyed. The refactoring lesson covers exactly this.