skip to content

In Terraform, how do `terraform output -json` and `terraform output -raw NAME` differ from plain `terraform output`, and how does each treat values marked sensitive?

level: middleimportance: nice to knowfreq 34%

answer

  1. all three read state, not the cloud
  2. one view for humans, two for scripts
  3. -raw is for command substitution
  4. -json labels sensitivity, does not hide it
  5. piping outputs into an artifact leaks them

basics

~20 s

Plain terraform output prints a human-readable list and shows sensitive values as <sensitive>. The -json flag emits machine-readable JSON of every output, and -raw NAME prints one value with no quotes or formatting. Both -json and -raw print sensitive values in clear text.

solid answer

~50 s

All three read the outputs recorded in state rather than re-planning. Plain `terraform output` is the human view: a list of `name = value` lines with sensitive ones shown as `<sensitive>`. `terraform output -json` emits a JSON object keyed by output name, each entry carrying `value`, `type`, and a `sensitive` boolean — that boolean is metadata, and the value beside it is printed in clear text. `terraform output -raw NAME` prints a single value with no quotes, no JSON, and no trailing decoration, which is what makes it usable in shell substitution; it only accepts values convertible to a string, so a list or map is rejected. Both machine-readable forms print sensitive values deliberately, because scripts need them — so a CI step that runs either one has just undone the redaction the plan gave you.

code

bash · 11 lines
bash
# Human view: sensitive values render as <sensitive>
terraform output

# Single value, unquoted - safe for command substitution
export DB_HOST="$(terraform output -raw db_endpoint)"

# Machine view: real values, with a sensitive flag beside each
terraform output -json | jq -r '.db_endpoint.value'

# Leaks every secret in the workspace - do not do this
terraform output -json > build-artifacts/outputs.json

go deeper

for a junior

Know the three forms: plain for reading on screen, -json for scripts, -raw NAME to drop a single value into a shell command substitution.

for a middle

Explain that all three read recorded state rather than the live cloud, and that -json and -raw print sensitive values in clear text while plain output shows <sensitive>.

for a senior

Point out the pipeline consequence: a job that redacts its plan but archives terraform output -json has published every secret. Prescribe fetching one named value, masking it, and never writing it to an artifact.

for a principal

Set the estate-wide rule for how values leave Terraform — a documented, minimal set of outputs consumed as data, secrets resolved from a secret store at runtime rather than exported, and CI policy that forbids dumping outputs wholesale.

## The three forms `terraform output` reads the **root module's** outputs out of state. It does not plan, does not refresh by default, and does not talk to your providers to compute anything — the values were recorded by the last apply. **Human form.** `terraform output` with no flags prints every root output as `name = value`, using HCL-ish rendering, with sensitive values replaced by `<sensitive>`. `terraform output NAME` prints just one, still quoted for strings. **JSON form.** `terraform output -json` prints a single JSON object keyed by output name: ```json { "db_endpoint": { "sensitive": false, "type": "string", "value": "db.internal:5432" }, "db_password": { "sensitive": true, "type": "string", "value": "s3cr3t-actual-value" } } ``` Note what that shows: `sensitive` is a *field describing* the value, sitting next to the value itself in clear text. `-json` is a data interface, not a redaction layer. Adding a name (`terraform output -json NAME`) narrows it to one entry, still wrapped in the same object shape — which is why scripts usually pipe the whole thing through `jq -r '.name.value'`. **Raw form.** `terraform output -raw NAME` prints the value alone, unquoted, with no surrounding structure — exactly what you want inside `$(...)`: ```bash export DB_HOST="$(terraform output -raw db_endpoint)" ``` `-raw` requires a single named output and only works for values that convert cleanly to a string: strings, numbers, booleans. Ask for a list, map, or object and Terraform errors rather than guessing a serialisation — use `-json` and `jq` for those. Like `-json`, `-raw` prints sensitive values in clear text. ## Why the machine forms are not redacted Because redacting them would make them useless. The point of `-raw` is to feed a value into another program; a placeholder would break every consumer. Terraform's position is that the sensitive mark protects against *incidental* disclosure — the value appearing in a diff nobody asked to see — not against a deliberate request for the value by someone who already has state access. Reading state and reading `-json` are the same privilege. The operational consequence is the part worth saying in an interview: a pipeline that carefully avoids printing the plan, but then runs `terraform output -json > outputs.json` and archives it as a build artifact, has published every secret in the workspace. So has one that does `echo "HOST=$(terraform output -raw ...)"` under `set -x`. If a job must pull a sensitive value, it should fetch exactly the one output it needs by name, keep it in a variable rather than a file, and register it with the CI system's own masking mechanism if one exists. ## Practical notes - These commands read **root** outputs only. A child module's output is invisible unless the root re-exports it. - They read the recorded state, so a value that has drifted in the real world is not detected here; `terraform output` is a lookup, not a check. - `-json` is the stable, parseable contract; the human form's rendering is not something to script against. - An output that does not exist in state — never applied, or removed — is an error, not an empty string, which is usually the behaviour you want in a pipeline. - If a value must never be persisted at all, an output is the wrong carrier; Terraform 1.10 added `ephemeral = true` outputs, which are not written to state and can only be consumed by a parent module, never read back with `terraform output`. ## The answer in one breath Plain is for humans and redacts; `-json` is the machine contract and labels sensitivity without hiding it; `-raw` is for shell substitution of one string-like value. Both machine forms print secrets in clear text on purpose.

  • Why does terraform output -raw fail for a list or map output?
    Because `-raw` writes the value as a bare string with no delimiters, and there is no unambiguous string form for a collection — any choice would silently mangle values containing the separator. Terraform errors instead of guessing. Use `-json` and parse it, for example `terraform output -json subnets | jq -r '.value[]'`.
  • A pipeline needs one sensitive output. What is the safe way to fetch it?
    Request that single output by name with `-raw`, capture it into a variable rather than a file, avoid `set -x` around the call, register it with the CI system's secret-masking API, and never archive it as an artifact. Better still, have the consuming system read the secret from a secret store directly so the pipeline never holds it.

saying these in an interview costs you the question

  • Assumes -json redacts sensitive values like the human output does
  • Thinks terraform output contacts the provider to fetch current values
  • Uses -raw on a map or list and expects a serialized string
  • Believes terraform output can read a child module's outputs directly
  • Archives terraform output -json as a build artifact

context