Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Terraform By James Joyner IV · · 9 min read

Terraform Error Guide: 'value depends on resource attributes that cannot be determined until apply' (apply-time unknowns)

Fix Terraform apply-time unknown values that break for_each keys, dynamic blocks, and conditionals using known keys and staged -target applies.

  • #terraform
  • #troubleshooting
  • #errors
  • #planning

Exact Error Message

When Terraform builds a plan it walks a dependency graph and tries to resolve every expression to a concrete value. If the shape of your configuration (how many instances of a resource, or which keys exist) depends on an attribute that only exists after a resource is created, the plan stops cold:

Error: Invalid for_each argument

  on main.tf line 18, in resource "aws_route53_record" "rec":
  18:   for_each = { for s in aws_subnet.app : s.id => s.cidr_block }

The "for_each" map cannot be determined until apply, so Terraform cannot
predict how many instances will be created. To work around this, use the
-target argument to first apply only the resources that the for_each value
depends on.

You will see close variants of this for count, dynamic blocks, and module meta-arguments. The common thread is always the same phrase: a value depends on resource attributes that cannot be determined until apply.

What the Error Means

Terraform runs in two phases. During plan, it computes the desired state without touching any real infrastructure. During apply, it actually creates and modifies resources, and only then does the cloud provider hand back generated attributes like IDs, ARNs, IP addresses, and CIDR ranges.

Some expressions can tolerate an unknown value: an attribute marked (known after apply) in the plan output is fine if it is just a string assigned to another resource’s argument. But meta-arguments are different. count, for_each, and the keys of a for_each map control how many resource instances exist and what their addresses are. Terraform must know those before it can build the graph, diff state, and show you an accurate plan.

If the value behind for_each or count is derived from aws_subnet.app[*].id and those subnets do not exist yet, Terraform genuinely cannot know how many records to plan. So it refuses, rather than guess. This is the “apply-time unknown” problem: a structural decision is gated on data that is not yet knowable at plan time.

Common Causes

  • for_each keys derived from computed IDs. Using s.id (a known-after-apply attribute) as a map key is the classic trigger. Values can be unknown; keys cannot.
  • count tied to a resource attribute. count = length(aws_instance.web) when the instances are created in the same apply.
  • Dynamic blocks fed by computed lists. A dynamic "ingress" block iterating over aws_security_group.other.id references that are not yet provisioned.
  • Module count/for_each on an output of another module. Module B’s instance count depends on Module A’s not-yet-created resources.
  • Conditionals over unknown booleans. for_each = var.enabled ? toset(aws_x.y[*].arn) : [] where the ARN set is unknown.
  • depends_on chains that defer creation. Adding depends_on to a data source or resource can push the dependency’s resolution to apply time.

How to Reproduce the Error

Save this as main.tf. It tries to create a Route 53 record per subnet, keyed by the subnet ID that AWS only assigns at creation:

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "app" {
  count      = 3
  vpc_id     = aws_vpc.main.id
  cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index)
}

resource "aws_route53_record" "rec" {
  # subnet IDs are unknown until apply -> invalid for_each
  for_each = { for s in aws_subnet.app : s.id => s.cidr_block }

  zone_id = var.zone_id
  name    = "node-${each.key}.internal"
  type    = "A"
  ttl     = 300
  records = [cidrhost(each.value, 4)]
}

Run a plan and Terraform rejects it because the map keys (s.id) are all (known after apply).

Diagnostic Commands

Use only read-only commands to confirm the cause. None of these change infrastructure.

# Validate syntax and basic semantics
terraform validate

# Produce a plan and read where the error points
terraform plan

# Inspect the dependency graph to see what feeds for_each
terraform graph | grep -A2 "aws_route53_record"

# Evaluate the problematic expression interactively
echo 'aws_subnet.app[*].id' | terraform console

# See what already exists in state (apply-time vs not-yet-created)
terraform state list | grep aws_subnet

In terraform console, an unknown value prints as a tuple of (known after apply) placeholders. That is the signal: if the keys are unknown, the for_each is invalid. You can also scan the plan output:

terraform plan -no-color | grep "known after apply"

If the attribute you are keying on shows up in that list, you have found the apply-time unknown.

Step-by-Step Resolution

There are two durable fixes and one tactical workaround.

1. Key by static, plan-time-known values (preferred). Restructure so the for_each keys come from input variables or locals that Terraform knows before apply. You can still reference computed attributes inside the values, just never in the keys.

variable "subnets" {
  type = map(object({
    cidr = string
  }))
  default = {
    app-a = { cidr = "10.0.0.0/24" }
    app-b = { cidr = "10.0.1.0/24" }
    app-c = { cidr = "10.0.2.0/24" }
  }
}

resource "aws_subnet" "app" {
  for_each   = var.subnets
  vpc_id     = aws_vpc.main.id
  cidr_block = each.value.cidr
}

resource "aws_route53_record" "rec" {
  # keys come from var.subnets, which is known at plan time
  for_each = var.subnets

  zone_id = var.zone_id
  name    = "node-${each.key}.internal"
  type    = "A"
  ttl     = 300
  # value can reference the computed attribute safely
  records = [aws_subnet.app[each.key].id == "" ? "" : cidrhost(each.value.cidr, 4)]
}

Now both resources share the same known key set (app-a, app-b, app-c), so Terraform can plan the records even though the subnet IDs are still unknown.

2. Two-stage -target apply (tactical workaround). When you cannot refactor immediately, create the dependency first, then let its attributes become known:

# Stage 1: create only the resources for_each depends on
terraform apply -target=aws_subnet.app

# Stage 2: full apply now that subnet IDs are known
terraform apply

After stage 1, the subnet IDs are real values in state, so the for_each map can be computed. Treat this as a stopgap; Terraform itself prints a warning that -target is meant for exceptional recovery, not routine workflow.

3. Apply the same principle to dynamic blocks, modules, and conditionals. For a dynamic block, build the iterator from a variable or local list, not a computed list. For module meta-arguments, pass a known map into the module rather than wiring it to another module’s resources. For conditionals, gate on a plain boolean variable (var.enabled) instead of the truthiness of an unknown set.

If you are tracking these failures across a fleet of pipelines, our incident-response dashboard can help triage recurring plan-time breakages.

Prevention and Best Practices

  • Derive structure from inputs. Map keys, counts, and iterator sets should trace back to variables and locals, never to (known after apply) attributes.
  • Separate “what exists” from “what it becomes.” Decide instance identity from configuration; let computed attributes flow only into argument values.
  • Run terraform plan in CI on every PR. Catch apply-time-unknown errors before merge, not during a production apply.
  • Split layered infrastructure into stacks. Put foundational resources (VPCs, subnets) in one root module/state and dependent resources in another, so the foundation’s outputs are already known when the second stack plans.
  • Avoid unnecessary depends_on. Implicit dependencies through references are usually enough; explicit depends_on can force values to be unknown at plan time.
  • Prefer for_each over count for sets of named things, so adding or removing one item does not reshuffle every other instance address.

Browse more guides in the Terraform category.

Frequently Asked Questions

Why can a value be (known after apply) in one place but break for_each? Because argument values are allowed to be unknown during plan, but meta-arguments are not. for_each, count, and map keys decide the shape of the plan, so Terraform must resolve them before it can diff state. An unknown shape is unrecoverable at plan time, while an unknown string argument simply gets filled in during apply.

Is -target a safe permanent solution? No. It is a recovery tool for breaking a circular bootstrap, and Terraform warns you when you use it. Routine reliance on -target produces partial applies and drift. Use it once to unblock, then refactor so keys come from known inputs.

Can I just wrap the value in try() or coalesce() to silence it? Not for the key. Those functions still produce an unknown value when their input is unknown, so the for_each remains undeterminable. They help only with optional argument values, not with structural meta-arguments.

Why does this happen on the first apply but not on the second? After the first successful apply, the dependency’s attributes exist in state and are known. On a fresh terraform apply with empty state, both resources are created in the same run, so the dependency’s IDs are still unknown when the dependent resource is planned.

Does splitting into separate modules fix it? It helps, but only if the modules are applied in separate stages with separate state. If both modules apply in one run, the downstream module still sees unknown values. The real fix is keying on inputs that are known before any apply.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.