Terraform "(known after apply)" Explained

Tested on Ubuntu 26.04 LTS (Resolute Raccoon)
Package terraform 1.15.8-1
hashicorp/random 3.6.3
hashicorp/local 2.5.2
Applies to Any host with Terraform installed
Lab environment Single Ubuntu VM with Terraform — Terraform lab environment on Ubuntu
Privilege Normal user
Scope Understanding Terraform known after apply — normal computed unknowns in plan and output, when unknown values break count and for_each instance identity, static map keys with apply-time values, deferred data source reads, module output propagation, terraform console diagnostics, and when not to force plan-time knowledge. Does not cover the full expressions catalog or every provider computed attribute.
Related guides Terraform expressions
count and for_each
Invalid for_each argument fixes
terraform console
terraform plan

(known after apply) in a plan means Terraform knows an expression is valid but cannot know its value until a provider operation runs. The value is unknown at plan time — not missing, not an error by itself.

text
resource creates → provider returns id, arn, hex, …
expression references that attribute → plan shows (known after apply)
apply completes → value is concrete in state and outputs

Each scenario uses its own directory under ~/terraform-labs/terraform-known-after-apply/. Examples use terraform_data, hashicorp/random, and hashicorp/local.

NOTE
Run terraform init in each new directory. For deeper expression rules, see Terraform expressions — this article focuses on when unknowns are normal versus when they block planning.

What known after apply means

On a first-time plan, computed resource attributes do not exist in state yet. Terraform marks them unknown instead of inventing a placeholder.

bash
mkdir -p ~/terraform-labs/terraform-known-after-apply/demos/normal-computed
cd ~/terraform-labs/terraform-known-after-apply/demos/normal-computed

Write a seed resource and outputs that reference its computed fields:

bash
cat > main.tf <<'EOF'
terraform {
  required_version = ">= 1.12.0"
}

resource "terraform_data" "seed" {
  input = "seed"
}

locals {
  derived = terraform_data.seed.output
}

output "seed_id" {
  value = terraform_data.seed.id
}

output "seed_output" {
  value = terraform_data.seed.output
}

output "derived" {
  value = local.derived
}
EOF

Initialize and plan without applying:

bash
terraform init -input=false

Plan before the first apply to see computed fields marked unknown:

bash
terraform plan -no-color -input=false
output
# terraform_data.seed will be created
  + resource "terraform_data" "seed" {
      + id     = (known after apply)
      + input  = "seed"
      + output = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + derived     = (known after apply)
  + seed_id     = (known after apply)
  + seed_output = (known after apply)

id, output, and local.derived all depend on the resource existing. Plan exits successfully — unknown values in the diff are expected before the first apply.


When unknown values are normal

Unknown values are safe when they affect argument values or outputs but not how many instances Terraform must create.

Safe at plan time Must be known at plan time
Resource argument set from another resource's computed attribute count value
Root or child module output referencing computed values for_each keys or set members
local or output expressions chaining computed attributes Resource addresses derived from unknown keys

After apply, run terraform plan again — the same attributes show concrete values and the output block updates.


When unknown values cause errors

Terraform must decide instance identity during planning for count and for_each. If keys or the count number depend on values known only after apply, plan fails — Terraform cannot build the instance map.

count has the same rule: the count expression must be known during plan. The lab focuses on for_each because that is the usual search path.

bash
mkdir -p ~/terraform-labs/terraform-known-after-apply/errors/for-each-unknown-keys
cd ~/terraform-labs/terraform-known-after-apply/errors/for-each-unknown-keys

Drive for_each from a random_id hex string — the set member stays unknown until apply:

bash
cat > main.tf <<'EOF'
terraform {
  required_version = ">= 1.12.0"
  required_providers {
    random = {
      source  = "hashicorp/random"
      version = "~> 3.6"
    }
  }
}

resource "random_id" "seed" {
  byte_length = 4
}

resource "terraform_data" "svc" {
  for_each = toset([random_id.seed.hex])
  input    = each.value
}
EOF

Initialize, then plan to surface the structural error:

bash
terraform init -input=false

Plan should fail because for_each set members stay unknown:

bash
terraform plan -no-color -input=false
output
Error: Invalid for_each argument

  on main.tf line 16, in resource "terraform_data" "svc":
  16:   for_each = toset([random_id.seed.hex])
    ├────────────────
    │ random_id.seed.hex 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.

The error names the unknown attribute and suggests the static-key map pattern. Full fix walkthrough: Invalid for_each argument fixes.


Fix for_each keys known only after apply

Keep keys in configuration; let values stay unknown until apply.

bash
mkdir -p ~/terraform-labs/terraform-known-after-apply/fixes/for-each-static-keys
cd ~/terraform-labs/terraform-known-after-apply/fixes/for-each-static-keys

Use a map with a literal key and a computed value:

bash
cat > main.tf <<'EOF'
terraform {
  required_version = ">= 1.12.0"

  required_providers {
    random = {
      source  = "hashicorp/random"
      version = "~> 3.6"
    }
  }
}

resource "random_id" "seed" {
  byte_length = 4
}

resource "terraform_data" "svc" {
  for_each = {
    app = random_id.seed.hex
  }

  input = each.value
}

output "instances" {
  value = {
    for k, v in terraform_data.svc : k => v.output
  }
}
EOF

Plan succeeds — instance app is identified even though random_id.seed.hex is still unknown:

bash
terraform init -input=false

Static map keys let plan succeed while argument values remain unknown:

bash
terraform plan -no-color -input=false
output
# terraform_data.svc["app"] will be created
  + resource "terraform_data" "svc" {
      + id     = (known after apply)
      + input  = (known after apply)
      + output = (known after apply)
    }

Plan: 2 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + instances = {
      + app = (known after apply)
    }

The key app is known; only input and the output map value remain unknown. That is the intended split for count and for_each with provider-computed data.


Data sources deferred until apply

Data sources normally read during plan. When a data source depends on a resource that is still being created, Terraform defers the read until apply.

bash
mkdir -p ~/terraform-labs/terraform-known-after-apply/demos/deferred-data
cd ~/terraform-labs/terraform-known-after-apply/demos/deferred-data

A local_sensitive_file writes content from random_id; the local_file data source waits on that resource:

bash
cat > main.tf <<'EOF'
terraform {
  required_version = ">= 1.12.0"

  required_providers {
    random = {
      source  = "hashicorp/random"
      version = "~> 3.6"
    }
    local = {
      source  = "hashicorp/local"
      version = "~> 2.5"
    }
  }
}

resource "random_id" "seed" {
  byte_length = 4
}

resource "local_sensitive_file" "secret" {
  content  = random_id.seed.hex
  filename = "${path.module}/.secret"
}

data "local_file" "secret" {
  filename   = local_sensitive_file.secret.filename
  depends_on = [local_sensitive_file.secret]
}

output "data_content" {
  value = data.local_file.secret.content
}
EOF

Plan marks the data read as deferred:

bash
terraform init -input=false

Plan marks the data source read as deferred until apply:

bash
terraform plan -no-color -input=false
output
# data.local_file.secret will be read during apply
  # (depends on a resource or a module with changes pending)
 <= data "local_file" "secret" {
      + content  = (known after apply)
      + filename = "./.secret"
      + id       = (known after apply)
    }

  # local_sensitive_file.secret will be created
  + resource "local_sensitive_file" "secret" {
      + content  = (sensitive value)
      ...
    }

Changes to Outputs:
  + data_content = (known after apply)

The <= prefix means read during apply, not an error. After apply, the data source content is known on the next plan.


Outputs and module propagation

Child module outputs that reference computed resources stay unknown at the root until apply — that is normal propagation, not a module bug.

bash
mkdir -p ~/terraform-labs/terraform-known-after-apply/demos/module-output/modules/child
cd ~/terraform-labs/terraform-known-after-apply/demos/module-output

Child module exposes a computed output:

bash
cat > modules/child/main.tf <<'EOF'
resource "terraform_data" "marker" {
  input = "from-child"
}

output "result" {
  value = terraform_data.marker.output
}
EOF

Root reads it:

bash
cat > main.tf <<'EOF'
module "child" {
  source = "./modules/child"
}

output "from_module" {
  value = module.child.result
}
EOF

Initialize the module tree, then plan from the root:

bash
terraform init -input=false

Root plan propagates unknown through the child module output:

bash
terraform plan -no-color -input=false
output
# module.child.terraform_data.marker will be created
  + resource "terraform_data" "marker" {
      + id     = (known after apply)
      + output = (known after apply)
    }

Changes to Outputs:
  + from_module = (known after apply)

Root outputs cannot be more specific than the child output they reference. See Terraform module input and output for wiring patterns.


Diagnose unknown values

Use terraform console to inspect an expression without applying. Reuse the demos/normal-computed/ configuration from earlier in this article:

bash
cd ~/terraform-labs/terraform-known-after-apply/demos/normal-computed

Initialize, then ask console for the seed resource output:

bash
terraform init -input=false

Pipe the seed output expression into console:

bash
echo 'terraform_data.seed.output' | terraform console
output
(known after apply)

The chained local should show the same unknown:

bash
echo 'local.derived' | terraform console
output
(known after apply)

Console confirms the dependency chain. Pair with terraform plan to see which resources and outputs carry unknowns in the diff. For graph-level dependencies, see terraform graph.


Do not fix normal unknowns

You do not need to eliminate every (known after apply) line from a plan.

  • Provider-computed IDs, ARNs, and checksums are unknown until create — that is by design
  • Outputs that surface those values will stay unknown on first plan
  • Unknown values inside a for_each map are fine when keys are static

Avoid workarounds that hide unknowns without fixing structure:

  • -target apply only to "unblock" for_each keys — use for one-off debugging, not as permanent design
  • try() or coalesce() around unknown keys — does not make keys known at plan time
  • Replacing for_each with count on an unknown length — count has the same plan-time requirement

Fix structural errors by redesigning keys and instance counts. Accept computed unknowns everywhere else.


Diagnostic checklist

Symptom Likely cause Action
(known after apply) on resource id or computed arg First plan before create Normal — apply, then re-plan
Invalid for_each argument + known only after apply Dynamic keys from computed attributes Static map keys; unknown values only
count value not known Count depends on computed attribute Rework to known count or use static for_each keys
<= data … read during apply Data source depends on pending resource Normal on first create; verify depends_on
Root output unknown, child resource unknown Module output propagation Normal until child resource exists
Unknown unexpectedly persists after resources already exist Dependency, provider-computed value, or refresh/state issue Run a normal terraform plan; use terraform plan -refresh-only when specifically checking state drift

References


Summary

(known after apply) means Terraform validly references a value that does not exist in state until apply. On a first plan, computed resource attributes, chained locals, outputs, and module outputs commonly show unknown — that is expected, not a failure.

Planning breaks when unknown values drive structure: for_each keys, set members, or count must be known early so Terraform can name instances. The fix is static keys with apply-time values, not forcing every provider attribute to appear in the plan diff.

Data sources that depend on resources still being created read during apply, marked with <= in the plan. Console and plan together show which expressions stay unknown and whether the issue is cosmetic or structural.

For expression syntax around unknowns, continue with Terraform expressions. When plan fails on for_each specifically, use Invalid for_each argument fixes.


Frequently Asked Questions

1. What does known after apply mean in Terraform?

Terraform cannot compute the value until a provider creates or updates the resource during apply. The expression syntax is valid, but the attribute does not exist in state yet, so plan marks it as unknown rather than guessing a value.

2. Is known after apply an error?

Usually no. Computed resource attributes, outputs that reference them, and child module outputs often show known after apply on a first-time plan. It becomes a problem only when Terraform must know the value during planning to decide instance count, for_each keys, or resource addresses.

3. Why does for_each fail with unknown values?

Terraform must know every for_each instance key during plan to build the resource graph. Keys derived from attributes that stay unknown until apply cannot identify instances, so plan fails with Invalid for_each argument.

4. How do I fix for_each when values are known only after apply?

Define map keys statically in configuration and put apply-time results in the values only, for example for_each = { app = random_id.seed.hex }. Terraform can plan instance app even when the hex string is still unknown.

5. Can terraform console show unknown values?

Yes. Before the first apply, console prints known after apply for expressions that depend on not-yet-created resources. That confirms the dependency chain without running apply.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)