skip to content

Terraform 1.10 introduced ephemeral values and 1.11 added write-only resource arguments. What problem do they solve that marking a value sensitive never could, and what constraint does a write-only argument place on your configuration?

level: middleimportance: nice to knowfreq 28%

answer

  1. not persisted, rather than not displayed
  2. alive only for one run
  3. nothing stored means nothing to diff
  4. the trigger is a version you bump
  5. rotation becomes a declared change

basics

~20 s

They keep a secret out of state and out of plan files entirely, rather than merely redacting it in output. The tradeoff: a write-only argument is never stored, so Terraform cannot diff it — you bump a paired version argument to make it re-send the value.

solid answer

~50 s

Marking a value sensitive changes how it renders; it still gets written to state and to any saved plan. Ephemeral values and write-only arguments change what is *persisted*. An ephemeral input variable, output or resource exists only for the duration of a single plan or apply — Terraform will refuse to store it, so a secret fetched at apply time never lands anywhere durable. A write-only argument on a managed resource, such as the AWS provider's `password_wo` on `aws_db_instance`, accepts a value, sends it to the API, and stores nothing. The constraint follows directly from that: with no stored copy there is nothing to compare against, so Terraform can never detect that the value changed. Write-only arguments are therefore paired with a version argument — `password_wo_version` — and you increment it when you want the new value sent. That makes rotation an explicit, declared act rather than something inferred from a diff.

code

hcl · 15 lines
hcl
variable "db_password" {
  type      = string
  ephemeral = true
}

resource "aws_db_instance" "main" {
  identifier          = "app-prod"
  engine              = "postgres"
  instance_class      = "db.t4g.medium"
  allocated_storage   = 20
  username            = "app"
  password_wo         = var.db_password
  password_wo_version = 1
  skip_final_snapshot = true
}

go deeper

for a junior

Know the headline distinction: sensitive hides a value in output, ephemeral and write-only stop it being stored at all. Knowing these exist is enough at this level.

for a middle

Explain the mechanics — an ephemeral value lives only for one run and may only flow into non-persisting places, and a write-only argument is sent and discarded — and state the consequence that nothing stored means nothing to diff.

for a senior

Weigh the tradeoff in production: invisible drift on that attribute, rotation as an explicit version bump reviewable in a pull request, patchy provider support, and where these sit relative to letting the platform manage the credential itself.

for a principal

Decide the estate-wide policy: which secrets are allowed to traverse Terraform at all, whether you require write-only where providers support it, and how you handle the long tail of resources that do not, given the migration cost of changing a live resource's password path.

## The gap these fill Before Terraform 1.10 there were two honest answers to "how do I keep this password out of state?" — don't put it in Terraform, or accept that it is in state and protect state accordingly. Sensitivity marking was never an answer: it governs rendering only. Ephemeral values and write-only arguments close that gap by adding a category of value that Terraform is *forbidden* from persisting. ## Ephemeral values (Terraform 1.10) Three things can be ephemeral: - **Ephemeral resources**, declared with the `ephemeral` block type. They are opened when needed during a run and closed at the end; nothing about them goes into state or the plan file. Providers implement them for exactly this shape of task — the AWS provider ships `ephemeral "aws_secretsmanager_secret_version"` for reading a secret at apply time. - **Ephemeral input variables**, declared with `ephemeral = true`. Terraform accepts the value for the run and refuses to write it anywhere durable. - **Ephemeral outputs**, which can pass such a value between modules within a run but not out into stored state. The rule Terraform enforces is that an ephemeral value may only flow into places that also do not persist: another ephemeral value, a provider configuration argument, or a write-only argument. Try to assign one to an ordinary resource argument and the configuration fails — which is the point. The prohibition is checked, not merely documented. ```hcl ephemeral "aws_secretsmanager_secret_version" "db" { secret_id = "prod/app/db" } ``` ## Write-only arguments (Terraform 1.11) A write-only argument is a managed-resource argument whose value is sent to the provider during apply and then discarded. It is never in state, never in the plan file, and never returned by a refresh. Providers name them with a `_wo` suffix, and the canonical example is RDS: `password_wo` alongside `password_wo_version` on `aws_db_instance`. ```hcl variable "db_password" { type = string ephemeral = true } resource "aws_db_instance" "main" { identifier = "app-prod" engine = "postgres" instance_class = "db.t4g.medium" allocated_storage = 20 username = "app" password_wo = var.db_password password_wo_version = 1 skip_final_snapshot = true } ``` ## The constraint, and why it is not a wart Terraform's entire model is *compare desired configuration to recorded state*. Remove the record and the comparison is impossible: Terraform genuinely cannot know whether the password you are supplying today differs from the one it sent last week. Rather than pretend, the design makes the trigger explicit. You bump `password_wo_version` to 2 and the plan shows an update that re-sends the value. Several consequences follow, and naming them is what separates a candidate who has read the release notes from one who has used the feature: - **Rotation becomes a declared change**, visible in a diff and reviewable in a pull request — arguably better than a silent value swap that shows as `(sensitive value) -> (sensitive value)`. - **Drift on that attribute is invisible.** If someone changes the password out of band, Terraform will not notice, because it has nothing to compare and the provider does not read it back. - **Not every argument supports it.** Write-only is opt-in per provider and per attribute; if the provider has not implemented it, the old rules apply. - **Only managed resources.** Data sources and outputs use the ephemeral mechanisms instead. ## Where they fit in the ladder Order the mitigations honestly. Best is not routing a secret through Terraform at all — for RDS, `manage_master_user_password = true` has AWS generate and hold the master password in Secrets Manager. Next best is write-only or ephemeral, where the value passes through the run and is never persisted. Below that sits everything about protecting the state that *does* contain the secret: backend access policy, encryption at rest, no state in Git. Sensitivity marking is not on this ladder at all; it belongs on a different one about not printing secrets into terminals and logs.

  • Why can't Terraform just detect that a write-only value changed?
    Because detection requires a stored prior value to compare against, and the whole point is that nothing is stored. The provider does not read it back either. So Terraform pairs the argument with a version number you control: incrementing it produces a planned update that re-sends the value, which makes rotation explicit and reviewable instead of inferred.
  • What happens if you assign an ephemeral value to an ordinary resource argument?
    Terraform rejects the configuration. Ephemeral values may only flow into other ephemeral values, provider configuration, or write-only arguments — places that also do not persist. Enforcing it at validation rather than trusting authors is what makes the guarantee meaningful, since a single careless assignment would otherwise put the secret straight back into state.
  • For an RDS master password specifically, is a write-only argument the best available answer?
    Not quite. Better is not handling the password at all: `manage_master_user_password = true` has AWS generate and store the master password in Secrets Manager, so no value passes through your configuration, your variables or your pipeline. Write-only is the right tool when you must supply a value Terraform did not create — an externally issued licence key or third-party API token.

saying these in an interview costs you the question

  • Thinks sensitive = true already achieves the same thing
  • Expects Terraform to detect drift on a write-only argument
  • Believes every provider argument can be made write-only
  • Forgets to bump the paired version and wonders why nothing applied
  • Assumes ephemeral values can be assigned to any resource argument

context