Terraform for_each: Stable IDs for Multiple Similar Resources
Learn how Terraform's for_each meta‑argument creates stable resource IDs, avoids off‑by‑one errors, and when to prefer it over count.
05 Jul 2026, 23:26 UTC

Problem: Managing Multiple Similar Resources Without Off‑by‑One Errors
When you need to create several alike AWS security groups (or any other resource) from a list of names, a naïve approach uses count and indexes the list. Adding or removing an entry shifts the indexes of the remaining items, causing Terraform to propose updates or replacements for unchanged resources.
Thesis: for_each Gives Stable Addresses Tied to Keys
The for_each meta‑argument iterates over a map or set of strings. Each instance gets an address that includes the key, so the identity of a resource survives changes to other entries.
Worked Example: AWS Security Groups via for_each
Define a variable map of names, then reference each key inside the resource block.
variable \"security_group_names\" {
type = map(string)
default = {
web = \"web-sg\"
db = \"db-sg\"
}
}
resource \"aws_security_group\" \"example\" {
for_each = var.security_group_names
name = each.value
description = \"SG for ${each.key}\"
ingress {
from_port = 22
to_port = 22
protocol = \"tcp\"
cidr_blocks = [\"0.0.0.0/0\"]
}
tags = {
Owner = each.key
}
}
Run the usual workflow in the directory containing this configuration:
terraform init– installs the AWS provider.terraform plan– shows a plan that includesaws_security_group.example[\"web\"]andaws_security_group.example[\"db\"]. No numeric indices appear.terraform apply– creates the two security groups.terraform state list– lists the two instances, each address containing its key.
To see the stability benefit, add a third entry:
variable \"security_group_names\" {
default = {
web = \"web-sg\"
db = \"db-sg\"
cache = \"cache-sg\"
}
}
Running terraform plan now displays only aws_security_group.example[\"cache\"] as a new resource; the existing web and db entries show no changes.
Trade‑offs and Limitations
- Key changes force replacement. If you alter a key in the map, Terraform treats it as a new resource and will destroy the old one, which may cause downtime for resources that cannot be updated in place.
- State size. Each instance adds a separate entry to the state file; large maps can increase plan time and state file size.
- Sensitive keys. Keys are stored in state (marked sensitive). Avoid using secrets such as passwords as
for_eachkeys. - No mixing with count. A resource cannot have both
countandfor_each; choose one mechanism per resource. - Provider constraints. Some providers expect a known count at read time; verify that the target resource works with
for_each(most do, but check the provider documentation).
Actionable Checklist
- Use
for_eachwhen you need a stable identifier for each instance and the set of items is known at plan time. - Prefer a map over a list of strings so you can assign meaningful keys.
- Review the plan after any change to the map; look for unexpected replacements caused by key modifications.
- Keep the map size moderate; monitor
terraform state listlength and plan duration. - Never place secret values directly as keys; if you need to iterate over secrets, consider using a separate data source or vault and avoid exposing them in state.
When the criteria above are met, for_each gives you predictable, auditable infrastructure changes and eliminates the off‑by‑one surprises that plague count-based loops.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.