How to Fix Terraform 'The each object cannot be used in this context' Error
Threat/Impact Level: HIGH | Exploitability/Downtime Risk: HIGH | Time to Fix: 5 mins
TL;DR
- What broke: A resource, module, or nested block references
each.keyoreach.valuebut the enclosing block has nofor_eachmeta-argument — Terraform's evaluation context has noeachobject to resolve. - How to fix it: Add
for_eachto the offending resource/module, or strip theeach.*references and switch tocount/static values. - Fast path: Use our Client-Side Sandbox above to paste your failing
.tffile and auto-refactor it — it detects every orphanedeachreference in one pass.
The Incident (What does the error mean?)
Raw error output from terraform plan or terraform apply:
Error: The 'each' object cannot be used in this context
on main.tf line 14, in resource "aws_iam_role_policy_attachment" "this":
14: role = each.value.role_name
A reference to "each.value" has been used in a context in which it
is unavailable, such as when the configuration for a resource has
not used the for_each argument.
Immediate consequence: terraform plan hard-exits. No state diff is produced. Every downstream resource depending on this output is also blocked. In a CI/CD pipeline, the entire apply stage fails and your infrastructure change is dead in the water.
The Attack Vector / Blast Radius
This is a compile-time evaluation failure, not a runtime one. Terraform's graph walker resolves references before any API call is made. When it encounters each.key or each.value with no for_each in scope, the entire configuration graph is poisoned — no resources in the run will apply, not just the broken one.
Common blast patterns:
- Copy-paste drift: An engineer copies a
for_each-driven resource block, strips thefor_eachline to create a singleton, but leaveseach.value.*references throughout the body. Plan fails immediately. - Module refactor regression: A child module that previously used
for_eachis called as a singleton. The module's internal resources still referenceeach.*, but the calling context no longer provides the iteration object. - Conditional resource anti-pattern: Developer uses
count = var.enabled ? 1 : 0but leaveseach.valuein the body instead of switching tovar.*references.countandeachare mutually exclusive meta-arguments. - Nested block contamination:
dynamicblocks inside a resource referenceeach.valuefrom an outer loop that doesn't exist at that nesting level.
Cascading failure risk in production pipelines: If your Terraform runs are automated via Atlantis, Spacelift, or GitHub Actions, this error will block every PR that touches the affected module — not just the one that introduced the bug. Teams running a monorepo with a shared module registry can have dozens of workspaces fail simultaneously.
How to Fix It (The Solution)
Scenario 1 — Basic Fix: Add the missing for_each
You want multiple instances. You just forgot to wire up for_each.
variable "iam_attachments" {
type = map(object({
role_name = string
policy_arn = string
}))
}
resource "aws_iam_role_policy_attachment" "this" {
+ for_each = var.iam_attachments
role = each.value.role_name
policy_arn = each.value.policy_arn
}
Scenario 2 — Basic Fix: You want a singleton — strip each.* entirely
You copied from a for_each block but only need one resource.
resource "aws_iam_role_policy_attachment" "this" {
- for_each = var.iam_attachments
- role = each.value.role_name
- policy_arn = each.value.policy_arn
+ role = var.role_name
+ policy_arn = var.policy_arn
}
Scenario 3 — count vs each conflict
You cannot use both count and for_each on the same resource. Pick one.
resource "aws_s3_bucket" "this" {
- count = var.create ? 1 : 0
- bucket = each.value.bucket_name
+ count = var.create ? 1 : 0
+ bucket = var.bucket_name
}
Enterprise Best Practice — Module-level for_each with validated input schema
For teams managing multiple environments or tenants, drive iteration from a strictly-typed map at the root module and pass it cleanly into child modules. Never let each.* leak across module boundaries.
# root/main.tf
module "iam_roles" {
source = "./modules/iam"
+ for_each = local.role_definitions
+ role_cfg = each.value
}
# modules/iam/variables.tf
variable "role_cfg" {
type = object({
role_name = string
policy_arn = string
})
+ validation {
+ condition = length(var.role_cfg.role_name) > 0
+ error_message = "role_name must be a non-empty string."
+ }
}
# modules/iam/main.tf
resource "aws_iam_role_policy_attachment" "this" {
- role = each.value.role_name # WRONG: each is not in scope inside a module body
+ role = var.role_cfg.role_name # CORRECT: consume the typed variable
- policy_arn = each.value.policy_arn
+ policy_arn = var.role_cfg.policy_arn
}
Key rule: Inside a module's resource blocks, each.* refers to the module's own for_each, not the caller's. If the module itself has no for_each, each is undefined. Always pass iteration values as explicit input variables.
💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.
Prevention in CI/CD
This class of error is 100% preventable before it ever reaches a shared pipeline.
1. terraform validate as a pre-commit gate
# .pre-commit-config.yaml
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.83.5
hooks:
- id: terraform_validate
- id: terraform_tflint
terraform validate catches each context errors at static analysis time with zero cloud credentials required.
2. TFLint with the terraform ruleset
# .tflint.hcl
plugin "terraform" {
enabled = true
preset = "recommended" # includes for_each/count consistency rules
}
3. Checkov policy — enforce for_each over count for map-typed variables
# checkov custom check (Python)
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
class ForEachContextCheck(BaseResourceCheck):
"""
Fail if a resource body references each.* without for_each declared.
This is a static AST check — catches the error before plan.
"""
ID = "CKV_CUSTOM_TF_001"
SUPPORTED_RESOURCES = ["*"]
4. OPA/Conftest policy for module interfaces
# policy/terraform/each_context.rego
package terraform.each_context
deny[msg] {
resource := input.resource_changes[_]
# Flag any plan that errors on each context — belt-and-suspenders
resource.change.before == null
resource.change.after == null
msg := sprintf("Resource '%s' produced no change — possible each context error.", [resource.address])
}
5. GitHub Actions — fail fast on validate
# .github/workflows/tf-validate.yml
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: "~1.8"
- run: terraform init -backend=false
- run: terraform validate # Hard fail — blocks PR merge
- run: tflint --recursive
Bottom line: Gate on terraform validate + TFLint in every PR. This error has a zero-second time-to-detect if your pipeline is correctly instrumented. There is no excuse for it reaching terraform apply.