Initializing Enclave...

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.key or each.value but the enclosing block has no for_each meta-argument — Terraform's evaluation context has no each object to resolve.
  • How to fix it: Add for_each to the offending resource/module, or strip the each.* references and switch to count/static values.
  • Fast path: Use our Client-Side Sandbox above to paste your failing .tf file and auto-refactor it — it detects every orphaned each reference 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:

  1. Copy-paste drift: An engineer copies a for_each-driven resource block, strips the for_each line to create a singleton, but leaves each.value.* references throughout the body. Plan fails immediately.
  2. Module refactor regression: A child module that previously used for_each is called as a singleton. The module's internal resources still reference each.*, but the calling context no longer provides the iteration object.
  3. Conditional resource anti-pattern: Developer uses count = var.enabled ? 1 : 0 but leaves each.value in the body instead of switching to var.* references. count and each are mutually exclusive meta-arguments.
  4. Nested block contamination: dynamic blocks inside a resource reference each.value from 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.

Related Diagnostics

"Part of the Syntax Utility Matrix."

View all 153 Syntax Tools →