Why Terraform for_each Beats Count for Stable Multi‑Resource Creation
When you need to create several similar resources, using for_each with a map of stable keys keeps addresses predictable, avoids unnecessary recreation, and makes refactoring safer. Learn how to refactor count‑based blocks, see a concrete example, and understand the trade‑offs.
01 Jun 2026, 05:48 UTC

Problem: Count’s Index‑Based Addresses Make Refactoring Painful
In many Terraform modules you’ll see patterns like:
resource "aws_s3_bucket" "example" {
count = length(var.bucket_names)
bucket = var.bucket_names[count.index]
acl = "private"
}
This works, but the generated addresses look like aws_s3_bucket.example[0], aws_s3_bucket.example[1], etc. The index is tied to the order of var.bucket_names. If you insert a new bucket in the middle of the list, Terraform will think the bucket at index 1 has moved to index 2, and it will plan to destroy aws_s3_bucket.example[1] and create a new one at aws_s3_bucket.example[2]. Even though the underlying bucket is the same, Terraform treats it as a new resource, causing unnecessary downtime and state churn.
Thesis: Use for_each with Stable Map Keys
Switching to for_each and a map of keys gives each resource a stable, human‑readable address:
resource "aws_s3_bucket" "example" {
for_each = var.bucket_map
bucket = each.key
acl = each.value.acl
}
Now the address is aws_s3_bucket.example["logs"] and aws_s3_bucket.example["data"]. Adding or removing entries in var.bucket_map does not shift the keys of existing resources, so Terraform only creates or destroys the affected ones.
Section 1 – How for_each Keeps Addresses Stable
for_each works by iterating over a collection – either a map or a set of strings. The resource address is built from the resource type, name, and the map key (or set element). Because the key is part of the address, Terraform can match the state entry to the code block regardless of the order of items in the collection. The key must be a string that will never change; if you change a key, Terraform will treat it as a new resource and destroy the old one.
Section 2 – Practical Refactor Pattern
Below is a minimal module that demonstrates the refactor from count to for_each. The module accepts a map of bucket definitions, each with an acl and optional tags.
variable "buckets" {
description = "Map of bucket configurations"
type = map(object({
acl = string
tags = optional(map(string), {})
}))
default = {
logs = { acl = "log-delivery-write" }
data = { acl = "private" }
}
}
resource "aws_s3_bucket" "this" {
for_each = var.buckets
bucket = each.key
acl = each.value.acl
tags = each.value.tags
}
output "bucket_arns" {
value = { for k, v in aws_s3_bucket.this : k => v.arn }
}
To test the stability, run:
terraform init
terraform plan
# Add a new bucket
# var.buckets = {
# logs = { ... }
# data = { ... }
# temp = { acl = "private" }
# }
terraform plan
You’ll see Terraform only adds aws_s3_bucket.this["temp"] and leaves the others untouched.
Section 3 – Trade‑offs & Limitations
- Key Immutability: Changing a key forces recreation. Pick keys that represent a stable identity (e.g., bucket name).
- Provider Constraints: Some providers still require a numeric index (e.g., certain IAM roles). In those cases
countmight be unavoidable. - State Migration: Converting an existing
countblock tofor_eachrequires a one‑time state import orterraform state mvto preserve existing resources. - Complex Objects: When the collection contains complex objects, ensure the keys are simple strings; Terraform 0.12+ handles nested maps, but version‑specific quirks may arise.
Section 4 – Actionable Checklist
- Audit modules for
countblocks that create a set of logically distinct resources. - Replace
countwithfor_eachusing a map where the key is a natural identifier. - Add a
validationblock to enforce unique, immutable keys. - Use
terraform state listbefore and after refactoring to confirm addresses remain stable. - Run
terraform planafter adding or removing an entry to verify only the intended resources change.
By following this pattern, you reduce accidental resource recreation, make your infrastructure code easier to read, and create a safer foundation for future refactors.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.