Skip to main content
DevOps Insightsguides & answers

Terraform · troubleshooting

Error: Invalid for_each argument

Terraform names every for_each instance by its key, so it needs those keys before it changes anything. When a key comes from something that does not exist yet, a function such as uuid() or timestamp(), or a secret, it refuses. The durable fix is in the error message itself: keep the keys in your configuration and let only the values wait for apply.

Reproduced on Terraform 1.14, . Where the text says “reproduced”, the behaviour was run on that version, not taken from documentation.

First published

New to this? Read what is actually happening first, then come back to the steps.

On this page · 9 sections

Before you start

  • The configuration and a terminal where terraform init has run, on the same Terraform version as your pipeline.
  • Permission to run terraform plan against the failing workspace and backend, and to create and delete a throwaway workspace there if you want to test from an empty state.
  • Knowing where it failed: a new or empty environment, or one that already has resources. The same files can pass in one and fail in the other.
  • Write access to the configuration repository, since the lasting fix is a change to how the keys are built.

Run this first

step 1 of 3
terraform plan -no-color 2>&1 | grep -E 'cannot be determined until apply|Sensitive values'

Look for "cannot be determined until apply" (attribute or impure function) versus "Sensitive values ... cannot be used"

The line under the headline separates the sensitive case from the other two. -no-color matters: without it Terraform writes colour codes into the pipe even when nothing is reading them as a terminal.

Work out which cause you have

A few questions to narrow the list. Every answer ends in a command that confirms or rules the cause out — this cannot see your cluster, so nothing here is a certainty until you have checked.

Narrow it down

0 answered · nothing is sent anywhere

What does the detail under "Invalid for_each argument" say?

Cause space

3 of 3 still possible

  • The keys come from something that does not exist yetCommon
  • A key comes from uuid(), timestamp() or bcrypt()Occasional
  • The collection is marked sensitiveOccasional

Nothing ruled out yet. Answer the question above and the branches your answer eliminates will strike through here.

Or diagnose it manually

In this order. The first command usually contains the whole answer.

  1. Step 1

    terraform plan -no-color 2>&1 | grep -E 'cannot be determined until apply|Sensitive values'

    The line under the headline separates the sensitive case from the other two. -no-color matters: without it Terraform writes colour codes into the pipe even when nothing is reading them as a terminal.

    Look for "cannot be determined until apply" (attribute or impure function) versus "Sensitive values ... cannot be used"

  2. Step 2

    grep -nE 'for_each.*(uuid|timestamp|bcrypt)\(' *.tf

    Rules out the impure-function case, which shares its message with the apply-time case.

    Look for A match is a lead, not a verdict: it is the cause only if the function feeds a key or a set element, not a map value

  3. Step 3

    orig=$(terraform workspace show); terraform workspace new for-each-check && terraform plan; terraform workspace select "$orig" && terraform workspace delete for-each-check

    Plans the configuration against an empty state without touching your real one. Existing state can make a broken for_each look fine.

    Look for The error appearing here but not in your usual workspace — the apply-time cause

Every cause, and how to fix it

Ordered by how often each one turns out to be the answer.

The keys come from something that does not exist yet

Common

The for_each expression builds its keys from an attribute only the provider can supply after creation — a generated ID, an ARN, an IP address. During planning that value is unknown, so Terraform cannot name the instances. This is also why it fails on new environments and not old ones: once the source resource exists, its attribute is known from state and the same expression resolves, until a change forces that resource to be replaced and the attribute is unknown again.

Confirm

grep -n 'for_each' *.tf

A for_each whose collection is built from another resource's attribute — something like toset([aws_instance.web.id]) or a set from a data source that depends on a resource

Fix

  • Key the collection by something you control in configuration, and move the apply-time value into the map's values: for_each = { primary = aws_instance.web.id }. Keys known, values free to be unknown.
  • If the keys genuinely come from another resource, derive them from that resource's configuration rather than its outputs — the same list you used to create it, not the IDs it returned.
  • The error suggests -target to apply the dependency first. HashiCorp's documentation says to use -target only in exceptional circumstances and not for routine operations, because it can lead to undetected configuration drift. It gets one environment past the error; it does not fix the configuration for the next empty state.
terraform plan

Re-plan after restructuring. Test against an empty state too — a new workspace — because existing state can hide the problem.

A key comes from uuid(), timestamp() or bcrypt()

Occasional

HashiCorp's documentation says keys cannot be the result of, or rely on, impure functions including uuid, bcrypt and timestamp, because Terraform defers evaluating them. Their result is unknown at plan time by design. The trap is the message: reproduced on 1.14, it is identical to the apply-time-attribute case and talks about resource attributes, even though none are involved.

Confirm

grep -nE 'for_each.*(uuid|timestamp|bcrypt)\(' *.tf

Any impure function contributing to a for_each key, directly or through a local that feeds it

Fix

  • Use a fixed key from configuration. A key's job is to identify the instance stably across runs — a value that changes on every plan cannot do that even in principle.
  • If you need a unique value per instance, put the impure function in an attribute, not the key.

The collection is marked sensitive

Occasional

Terraform refuses sensitive values in for_each because the key appears in instance addresses and plan output. HashiCorp's documentation gives the reason: Terraform uses the value to identify the resource instance and always discloses it in UI output. This one announces itself — the detail line says sensitive values cannot be used.

Confirm

grep -nB2 -A4 'sensitive *= *true' *.tf

The variable or output feeding for_each, marked sensitive — or a value derived from one

Fix

  • Key on something that is not secret — a name or an index — and keep the secret in an attribute of each instance.
  • Only if the keys genuinely are not secret, wrap them with nonsensitive(). Doing that to a value that is secret writes it into plan output, which is exactly what the check exists to prevent.

Understanding it properly

Skip this if you are mid-incident — the working part of the page is above. Worth reading afterwards, because understanding the mechanism is what stops the next one.

In plain terms

for_each makes one copy of a resource for each item in a map or set, and names every copy by its key. Terraform has to know all of those names before it changes anything. This error means at least one key will only be known after apply, such as an ID that does not exist yet, or that a key is a secret Terraform will not print.

A key that does not exist yet, reproduced

Reproduced with Terraform 1.14.0, the built-in terraform_data resource and the local backend, starting from an empty state. terraform_data.per_id sets for_each = toset([terraform_data.source.id]), and terraform_data.source has not been created yet. Above the error, Terraform 1.14 also printed the plan for terraform_data.source under "Terraform planned the following actions, but then encountered a problem"; that part is left out. The output contained no user, host or path details.

Command

terraform plan

Output

Error: Invalid for_each argument

  on main.tf line 6, in resource "terraform_data" "per_id":
   6:   for_each = toset([terraform_data.source.id])
    ├────────────────
    │ terraform_data.source.id is a string, known only after apply

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.

When working with unknown values in for_each, it's better to use a map value
where the keys are defined statically in your configuration and where only
the values contain apply-time results.

Alternatively, you could use the -target planning option to first apply only
the resources that the for_each value depends on, and then apply a second
time to fully converge.

How to read it

  • on main.tf line 6 and the quoted line point at the for_each expression, not at the resource it is waiting for.
  • terraform_data.source.id is a string, known only after apply names the unknown value. With uuid() or timestamp() called directly in for_each this line is missing, while the paragraphs below are word for word the same. Through a local, both cases print local.<name> is a string, known only after apply, so follow the local to where its value comes from.
  • The second paragraph is the durable fix: a map with keys written in configuration, such as { primary = terraform_data.source.id }, planned cleanly in the same reproduction.
  • The -target paragraph is a one-off workaround. HashiCorp's plan documentation says it is not recommended for routine operations.
  • The command exited 1 here. With terraform_data.source already in state, the same files planned with exit 0.

Words used on this page

for_each
A meta-argument that creates one instance of a resource or module for each element of a map or a set of strings.
Instance key
The map key or set element that names one instance, as in terraform_data.per_id["primary"]. Terraform uses it to match configuration to state.
Known after apply
A value Terraform can only learn by creating or changing something, such as an ID the provider returns. While planning it is a placeholder, shown as (known after apply).
Impure function
A function whose result changes from call to call, such as uuid(), timestamp() or bcrypt(). Terraform evaluates these late, so their results are unknown while planning.
Sensitive value
A value marked sensitive, for example a variable with sensitive = true. Terraform hides it in output, so it refuses it as a key, because keys are always shown.
-target
A plan option that limits a run to one resource and what it depends on. HashiCorp recommends it only for exceptional cases, not routine use.
Workspace
A separate state for the same configuration. A new workspace starts empty, which makes it a safe place to plan from scratch.
What is actually happening

Every instance a for_each creates is addressed by its key — resource["key"] — and that address is how Terraform matches configuration to state. It has to know every address before it can plan, because the plan is a list of what happens at each one. HashiCorp's documentation states the rule: the keys of the map, or all values in a set of strings, must be known values, and anything that is only known after apply will result in an error.

The confusing part is that one headline covers three different problems. Reproduced on Terraform 1.14, a key derived from a resource attribute and a key derived from uuid() produce word-for-word the same message — one that says the set includes values derived from resource attributes that cannot be determined until apply. If your key comes from uuid() or timestamp(), you will search your configuration for a resource attribute that is not there. A sensitive value is the third case, and it is the only one with its own explanation.

The second confusing part is that the same configuration can work in one place and fail in another. Against an existing environment, the resource your key depends on is already in state, so its attribute is known and the plan succeeds, unless the same plan replaces that resource, which makes the attribute unknown again. Against an empty state — a new workspace, a new region, a fresh CI run — that resource does not exist yet, so the attribute is unknown and the plan fails. Reproduced on 1.14: same files, exit 0 against existing state, exit 1 against an empty one.

Terraform's own message points at the fix: use a map whose keys are written in your configuration, and let only the values contain apply-time results. Values may be unknown during planning; keys may not. Reproduced: a map with a fixed key and a value from a not-yet-created resource plans cleanly.

How to tell this is your problem
WhereWhat you see
terraform planError: Invalid for_each argument
terraform plan, the detailThe "for_each" set includes values derived from resource attributes that cannot be determined until apply
terraform plan, the detailSensitive values, or values derived from sensitive values, cannot be used as for_each arguments
Across environmentsThe same code plans in an existing environment and fails in a new one or in CI from empty state

How to know it is actually fixed

  • terraform plan succeeds against existing state and against an empty one — a fresh workspace, a new environment, a CI run from scratch.
  • Instance addresses in the plan use keys you recognise from configuration, not IDs or generated values.
  • Re-running plan with nothing changed shows no changes; keys that drift between runs mean something impure is still in them.

Stopping it happening again

  • Key for_each by values written in configuration. Treat any for_each built from another resource's outputs as a bug waiting for an empty state.
  • Plan new environments from empty state in CI before they are needed, so this surfaces in review rather than on the day the environment is created.
  • Keep secrets out of keys. Keys show up in addresses, plans and logs.
  • Use -target as a one-off recovery tool, as HashiCorp's documentation advises, and never as the way a configuration is normally applied.

Security considerations

  • Keys are printed in every instance address, in plan output, logs and state. Reproduced on Terraform 1.14.0: keys passed through nonsensitive() appeared in plain text as terraform_data.unwrapped["alpha"]. Never build keys from secrets.
  • Use nonsensitive() only when the keys really are not secret. HashiCorp's function reference warns that using it indiscriminately shows sensitive values clearly in Terraform's output.
  • When only a map's values are secret, take the keys with a for expression, as HashiCorp documents: toset([for k, v in local.map : k]). Reproduced: this planned cleanly when the values were marked sensitive, but still failed when the whole variable was marked sensitive.
  • Treat a -target apply as unfinished. Terraform 1.14.0 printed "Applied changes may be incomplete" afterwards, so firewall rules, policies or alarms elsewhere in the configuration may not exist yet. Run a full terraform plan straight after.
  • The throwaway workspace used in diagnosis lives in your real backend. Only plan in it, never apply, and delete it afterwards; Terraform refuses to delete a workspace that tracks resources unless you add -force.

Sources

Behaviour described here is drawn from official documentation. Where a figure could not be confirmed on an official page it is attributed in the text rather than stated as canonical.

Did this get you to an answer?

No text box on purpose — please do not paste production logs anywhere

Something wrong or out of date? Report it