TerraformPilot

Troubleshooting

Fix Terraform Error - Invalid for_each Argument

Fix the Terraform invalid for_each argument error when keys depend on unknown values. Covers static keys, targeting, locals, and for_each best practices.

LLuca Berton2 min read

Quick Answer

#

The for_each set contains values derived from resource attributes that aren't known until apply time. Terraform needs all for_each keys at plan time. Replace computed values with static variables, pre-defined maps, or split into two applies.

The Error

#

When running terraform plan:

Error: Invalid for_each argument
 
  on main.tf line 10, in resource "aws_route53_record" "records":
  10:   for_each = toset(aws_acm_certificate.main.domain_validation_options[*].resource_record_name)
 
The "for_each" set includes values derived from resource attributes
that cannot be determined until apply, and so Terraform cannot
determine the full set of keys that will identify the instances of
this resource.
Error: Invalid for_each argument
 
  on main.tf line 5, in resource "aws_subnet" "private":
  5:   for_each = toset(data.aws_availability_zones.available.names)
 
The "for_each" argument depends on resource attributes that cannot
be determined until apply.

What Causes This Error

#

Terraform must know every key in for_each during the planning phase to build the resource graph. When keys come from resources that don't exist yet, Terraform can't determine the set.

Common Triggers

#

ACM certificate validation records:

# BAD — domain_validation_options not known until cert is created
resource "aws_route53_record" "validation" {
  for_each = {
    for dvo in aws_acm_certificate.main.domain_validation_options : dvo.domain_name => dvo
  }
}

Subnets from a new VPC:

# BAD — subnet IDs not known until VPC is created
resource "aws_instance" "workers" {
  for_each  = toset(aws_subnet.private[*].id)
}

Dynamic outputs from modules:

# BAD — module output not known at plan time
resource "aws_security_group_rule" "rules" {
  for_each = module.network.security_group_rules
}

How to Fix It

#

Solution 1: Use Static Variables for Keys

#

Define the keys as variables instead of deriving them from resources:

variable "availability_zones" {
  type    = list(string)
  default = ["us-east-1a", "us-east-1b", "us-east-1c"]
}
 
resource "aws_subnet" "private" {
  for_each          = toset(var.availability_zones)
  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet(var.vpc_cidr, 8, index(var.availability_zones, each.value))
  availability_zone = each.value
}

Solution 2: Use a Pre-Defined Map

#
variable "servers" {
  type = map(object({
    instance_type = string
    subnet_tier   = string
  }))
  default = {
    "web-1" = { instance_type = "t3.micro",  subnet_tier = "public" }
    "web-2" = { instance_type = "t3.micro",  subnet_tier = "public" }
    "api-1" = { instance_type = "t3.small",  subnet_tier = "private" }
    "db-1"  = { instance_type = "r5.large",  subnet_tier = "private" }
  }
}
 
resource "aws_instance" "servers" {
  for_each      = var.servers  # Keys known at plan time
  ami           = data.aws_ami.ubuntu.id
  instance_type = each.value.instance_type
 
  tags = {
    Name = each.key
  }
}

Solution 3: Fix ACM Certificate Validation (Common Pattern)

#

The ACM validation pattern is the most common trigger. Use the correct approach:

resource "aws_acm_certificate" "main" {
  domain_name               = var.domain_name
  subject_alternative_names = var.san_domains
  validation_method         = "DNS"
 
  lifecycle {
    create_before_destroy = true
  }
}
 
# Use { for } expression with the certificate's known structure
resource "aws_route53_record" "validation" {
  for_each = {
    for dvo in aws_acm_certificate.main.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }
 
  zone_id = var.zone_id
  name    = each.value.name
  type    = each.value.type
  ttl     = 60
  records = [each.value.record]
}
 
resource "aws_acm_certificate_validation" "main" {
  certificate_arn         = aws_acm_certificate.main.arn
  validation_record_fqdns = [for record in aws_route53_record.validation : record.fqdn]
}

If the above still fails (first-time apply), use -target:

terraform apply -target=aws_acm_certificate.main
terraform apply

Solution 4: Split into Two Applies with -target

#

When restructuring isn't practical:

# Step 1: Create the resources that produce dynamic values
terraform apply -target=aws_vpc.main -target=aws_subnet.private
 
# Step 2: Now all values are known
terraform apply

Solution 5: Use locals with Known Keys

#

Pre-compute the map from known inputs:

variable "domain_names" {
  type    = list(string)
  default = ["example.com", "www.example.com", "api.example.com"]
}
 
locals {
  # Map with known keys — domain names are static
  domain_map = { for d in var.domain_names : d => d }
}
 
resource "aws_acm_certificate" "main" {
  domain_name               = var.domain_names[0]
  subject_alternative_names = slice(var.domain_names, 1, length(var.domain_names))
  validation_method         = "DNS"
}

Solution 6: Use count as a Workaround

#

When for_each can't work, count with a known number sometimes can:

variable "subnet_count" {
  type    = number
  default = 3
}
 
resource "aws_subnet" "private" {
  count             = var.subnet_count
  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet(var.vpc_cidr, 8, count.index)
  availability_zone = data.aws_availability_zones.available.names[count.index]
}

for_each vs count Decision Guide

#
ScenarioUse for_eachUse count
Known set of named resources✅ Best fit⚠️ Index-based
Number of resources from variable⚠️ Convert to map✅ Simple
Resources from computed values❌ Won't work at plan❌ Won't work at plan
Need stable resource addresses✅ Key-based❌ Index shifts on removal

Troubleshooting Checklist

#
  1. ✅ Does for_each reference any resource that's being created in the same apply?
  2. ✅ Can you replace computed keys with static variables?
  3. ✅ Is this the ACM certificate validation pattern?
  4. ✅ Can you use -target for the first apply?
  5. ✅ Would count with a known number work instead?
  6. ✅ Can you pre-define the map in a variable or local?

Prevention Tips

#
  • Design for_each with known keys — never derive keys from resources in the same config
  • Use variables for sets — pass the set of keys as input, not as computed output
  • Document two-step applies — if -target is needed on first run, document it in README
  • Prefer maps over computed setsmap(object(...)) variables are always known at plan
  • Test with terraform plan on clean state — catches this error before it hits CI
#

Conclusion

#

Terraform requires all for_each keys at plan time. If keys come from resources being created in the same apply, Terraform can't build the graph. Use static variables, pre-defined maps, or split into two applies. For the common ACM validation pattern, use the { for } expression with domain names as keys.

#for-each#apply#planning

Share this article