Fix OpenTofu Ephemeral Value in Non-Ephemeral Context
OpenTofu throws "Ephemeral value used in non-ephemeral context" when a temporary value leaks toward state. Here is what triggers it and the exact fix.
You added an ephemeral resource or an ephemeral = true variable in OpenTofu 1.11, wired it into a normal resource argument, and the plan died with Ephemeral value used in non-ephemeral context. The short version: OpenTofu refuses to let a value it promised never to persist flow into a place that would write it to state or plan. The fix is almost always one of three moves: send the value into a write-only (_wo) argument instead of a regular one, mark the output or variable that carries it as ephemeral = true, or keep the whole chain ephemeral so nothing downstream tries to store it. This walks through why the error fires and how to pick the right fix.
What the error is actually telling you
Ephemeral values are OpenTofu’s mechanism for handling secrets and other temporary data that must never land in terraform.tfstate or a plan file. An ephemeral resource block, an ephemeral input variable, and an ephemeral output all produce values that exist only during a single operation. OpenTofu tracks that “ephemeral” taint through every expression, and the moment a tainted value reaches a context that persists data, it stops the run rather than silently leaking the secret.
The restricted contexts are consistent and worth memorizing:
| Context | Ephemeral value allowed? |
|---|---|
| A regular (stateful) resource argument | No |
A write-only argument (suffix _wo) | Yes |
| A root or child module output | No, unless the output is ephemeral = true |
A local that feeds a non-ephemeral context | No |
| A provider or provisioner configuration block | Yes |
| Another ephemeral resource’s arguments | Yes |
Read that table as one rule: an ephemeral value can only go somewhere that also refuses to persist it. Everything else is a hard error by design. If you have hit persistence problems from the other direction, where values you wanted in state got locked or lost, the mechanics of what OpenTofu keeps and why are covered in Terraform State Locking: A Guide for Growing Teams.
Reproduce it in ten lines
Here is the smallest config that triggers the error. It reads a database password from an ephemeral resource and tries to hand it to a normal argument:
ephemeral "random_password" "db" {
length = 24
}
resource "aws_db_instance" "main" {
identifier = "app-db"
password = ephemeral.random_password.db.result
}
Run a plan and OpenTofu rejects it before touching the provider:
$ tofu plan
Error: Ephemeral value used in non-ephemeral context
on main.tf line 7, in resource "aws_db_instance" "main":
7: password = ephemeral.random_password.db.result
Ephemeral values cannot be assigned to arguments that OpenTofu persists to
state. Use a write-only argument or mark the destination as ephemeral.
The provider never runs. This is a static check in the language layer, which is why no AWS call is made and no partial state is written. That is the whole point: the guard fires before the value can escape.
Fix 1: send it to a write-only argument
Most real cases are this one. You have a secret and you want it on a managed resource without storing it. That is exactly what write-only arguments exist for. They carry the _wo suffix, accept ephemeral values, and are always written to state and plan as null. Many provider resources expose a _wo twin of their sensitive argument, paired with a _wo_version argument you bump to force a new write.
Rewrite the failing example against a resource that supports write-only arguments:
ephemeral "random_password" "db" {
length = 24
}
resource "aws_secretsmanager_secret_version" "db" {
secret_id = aws_secretsmanager_secret.db.id
secret_string_wo = ephemeral.random_password.db.result
secret_string_wo_version = 1
}
secret_string_wo takes the ephemeral value and never records it. When you rotate the password, you change the value and increment secret_string_wo_version so OpenTofu knows to send the new secret on the next apply. The version integer is the only thing that lands in state. HashiCorp’s write-only arguments reference documents the same model OpenTofu implements, including which core arguments pair with a version.
The common mistake here is assuming every argument has a _wo version. They do not. Write-only support is per-argument and per-provider, so check the resource’s schema. If the argument you need has no write-only variant yet, that is a provider gap, not something you can force from configuration.
Fix 2: mark the output ephemeral
The second trigger is exporting an ephemeral value. This fails:
output "db_password" {
value = ephemeral.random_password.db.result
}
An ordinary output is stored, so OpenTofu blocks it. If a parent module genuinely needs to consume this value during the same operation (to feed it into another ephemeral context), mark the output ephemeral:
output "db_password" {
value = ephemeral.random_password.db.result
ephemeral = true
}
An ephemeral = true output can only be consumed by another ephemeral context in the calling module. You cannot mark an output ephemeral and then assign it to a normal resource argument upstream; you would just move the same error one module up. The ephemeral values documentation spells out the propagation rules across module boundaries.
Fix 3: keep the whole chain ephemeral
The subtle version of this error comes through a local. A local value built from an ephemeral expression inherits the ephemeral taint:
locals {
conn = "postgres://admin:${ephemeral.random_password.db.result}@db:5432"
}
local.conn is now ephemeral. Use it in a provisioner or a provider block and OpenTofu is happy. Assign it to a stored argument and you get the same error, now pointing at the local instead of the resource. The fix is not to launder the value through the local; it is to make sure the local’s destination is also ephemeral. If you find yourself wanting to store local.conn, step back, because that means you are trying to persist a secret, which is the exact thing the ephemeral system is stopping.
A full working example
Here is the pattern most teams actually want: pull a secret from a store at apply time and set a database password without ever writing the secret to state.
ephemeral "aws_secretsmanager_secret_version" "db" {
secret_id = "prod/app/db-password"
}
resource "aws_db_instance" "main" {
identifier = "app-db"
engine = "postgres"
instance_class = "db.t3.medium"
allocated_storage = 20
username = "appuser"
password_wo = ephemeral.aws_secretsmanager_secret_version.db.secret_string
password_wo_version = 3
}
Apply it and check what got stored:
$ tofu apply
$ tofu show -json | grep -c '"password_wo"'
The password value is absent from state; only password_wo_version = 3 is recorded. Rotate by updating the secret in Secrets Manager and bumping the version integer. This is the design working as intended: the secret transits the operation and vanishes, and your state file is safe to store in the same backend as everything else.
Why OpenTofu is this strict
It is tempting to read the error as OpenTofu being pedantic, but the strictness is the feature. State files are the single most common source of leaked infrastructure secrets, because they get committed, copied into CI logs, and shared in backends with loose access. By making it a compile-time error to route an ephemeral value anywhere persistent, OpenTofu removes the entire class of “oops, the password is in the plan output” incidents. The tradeoff is that you have to be explicit about where secrets are allowed to flow, which is a good constraint to have enforced by the tool rather than by a code review someone was too rushed to do. If your review process is where these decisions get made, Terraform Testing Best Practices: Beyond Plan and Pray covers how to catch this class of problem earlier.
Quick triage checklist
When you hit Ephemeral value used in non-ephemeral context, work through this in order:
- Read the line the error points to. It names the exact argument, output, or local that broke the rule.
- If it is a resource argument, look for a
_wovariant of that argument and switch to it, adding the matching_wo_version. - If it is an output, decide whether the consumer is ephemeral. If yes, add
ephemeral = true. If no, you are trying to persist a secret and should not. - If it is a local, trace where the local is used and make that destination ephemeral too.
- If no write-only variant exists for the argument you need, check the provider version and its changelog. Write-only support is still expanding across providers.
Nine times out of ten it is case two: a value that should have gone into a _wo argument was pointed at the regular one. Fix that and the plan goes green. For a different OpenTofu failure mode that also blocks a clean run, see Fix OpenTofu Registry Timeout Errors.
Related articles
Get the next article in your inbox
Practical DevOps tips, tutorials, and guides. No spam, unsubscribe anytime.