Terraform's for_each meta-argument requires a set or map, not a list. Learn how to convert lists to sets and fix this common configuration error.
The for_each meta-argument in Terraform is used to create multiple instances of a resource based on a collection of values. However, for_each has strict type requirements: it only accepts sets (set(string)) or maps. When you attempt to use a list, tuple, or other unsupported data type with for_each, Terraform returns this error because it cannot iterate over the value in the expected way. The error occurs at configuration validation time, before any resources are created. This is a configuration syntax issue, not a runtime problem. The solution involves converting your data to the correct type using Terraform functions like toset() or by restructuring your data using for expressions.
If you have a variable declared as list(string), wrap it with the toset() function when passing it to for_each:
variable "instance_names" {
type = list(string)
default = ["web-server", "app-server", "db-server"]
}
resource "aws_instance" "example" {
for_each = toset(var.instance_names)
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
tags = {
Name = each.value
}
}The toset() function converts your list into a set, which for_each requires. Each element becomes accessible via each.value.
When working with complex data structures like lists of objects, use a for expression to create a map:
variable "instances" {
type = list(object({
name = string
type = string
}))
default = [
{ name = "web", type = "t2.micro" },
{ name = "app", type = "t2.small" }
]
}
resource "aws_instance" "example" {
for_each = { for item in var.instances : item.name => item }
ami = "ami-0c55b159cbfafe1f0"
instance_type = each.value.type
tags = {
Name = each.key
}
}This creates a map where keys are instance names and values are the full objects. Access properties via each.value.property.
When using data sources or locals to populate for_each, ensure the output is a set or map. If it's a list, apply toset():
data "aws_availability_zones" "available" {
state = "available"
}
# This will fail - aws_availability_zones returns a list
# resource "aws_subnet" "example" {
# for_each = data.aws_availability_zones.available.names # ERROR!
# }
# Correct - wrap with toset()
resource "aws_subnet" "example" {
for_each = toset(data.aws_availability_zones.available.names)
vpc_id = aws_vpc.main.id
cidr_block = "10.0.${index(tolist(each.value), each.key)}.0/24"
availability_zone = each.value
}The best approach is to declare your variables with the correct type annotation:
# Use set(string) for collections of strings
variable "environment_names" {
type = set(string)
default = ["dev", "staging", "prod"]
}
# Use map() for key-value data
variable "resource_tags" {
type = map(string)
default = {
environment = "production"
team = "platform"
}
}
# Now for_each works directly without conversion
resource "aws_instance" "by_env" {
for_each = var.environment_names
# ...
}
resource "aws_s3_bucket_tag" "tags" {
for_each = var.resource_tags
# ...
}If for_each depends on computed values that aren't known until apply time, restructure your configuration. Terraform requires for_each values to be known during the plan phase:
# Problem: Using outputs that depend on created resources
# for_each = aws_instance.created[*].id # ERROR - can't predict count until apply
# Solution: Define keys statically, put apply-time values in map values
resource "aws_instance" "servers" {
for_each = {
"server1" = {}
"server2" = {}
}
# ...
}
resource "aws_eip" "servers" {
for_each = aws_instance.servers
instance = each.value.id
}Keep the iteration keys static and place computed values in the map values instead.
After making your changes, validate your configuration:
terraform validateIf successful, you'll see: "Success! The configuration is valid."
Then run plan to see what Terraform intends to create:
terraform planThe error should now be resolved, and you can proceed with apply.
When working with sensitive values, you cannot use them directly in for_each because Terraform cannot predict the for_each value during planning. To work around this, split sensitive values from keys: declare keys as a static set/map, and reference sensitive values only in the map values.
For resource attributes that can't be determined until apply time, restructure to keep iteration keys static (defined directly in your config) and place the computed values in the map values instead. This allows Terraform to predict the for_each behavior during planning.
On older Terraform versions (pre-0.12), for_each was not available; use count with index() instead, though for_each is preferred in modern configurations.
Error: Error rendering template: template not found
How to fix "template not found" error in Terraform
Error: Error generating private key
How to fix 'Error generating private key' in Terraform
Error creating Kubernetes Service: field is immutable
How to fix "field is immutable" errors in Terraform
Error: Error creating local file: open: permission denied
How to fix "Error creating local file: permission denied" in Terraform
Error: line endings have changed from CRLF to LF
Line endings have changed from CRLF to LF in Terraform