skip to content

How does Pulumi keep secret values out of plaintext in stack config and in state, and what is a stack's "secrets provider"?

level: middleimportance: must knowfreq 60%

answer

  1. marked, then encrypted before it is written
  2. secure: entry, not a plaintext value
  3. secretness travels with the value
  4. checkpoint holds ciphertext too
  5. passphrase, cloud KMS, or the service key

basics

~20 s

Pulumi encrypts values you mark as secret before they are written anywhere. pulumi config set --secret stores ciphertext in Pulumi.<stack>.yaml, secretness propagates into the state checkpoint, and the secrets provider — a passphrase, a cloud KMS key, or the Pulumi Cloud per-stack key — holds the encryption key.

solid answer

~50 s

Secrets in Pulumi are encrypted, not just hidden. `pulumi config set --secret dbPassword s3cr3t` writes a `secure:` ciphertext entry into `Pulumi.<stack>.yaml`, so the file is safe to commit. That secretness is sticky: when the value flows into a resource argument, anything derived from it is marked secret too and is stored **encrypted inside the state checkpoint**, so `pulumi stack export` shows ciphertext and the CLI prints `[secret]` in previews. Which key does the encrypting is the stack's *secrets provider*, chosen at `pulumi stack init --secrets-provider=...`: the Pulumi Cloud service key by default when logged into the service, or `passphrase`, or a cloud KMS URL such as `awskms://`, `gcpkms://`, `azurekeyvault://` or `hashivault://`. The passphrase provider stores an `encryptionsalt` in the stack file and needs `PULUMI_CONFIG_PASSPHRASE` set wherever you run. You can still reveal values deliberately with `pulumi config get --show-secrets` or `pulumi stack output --show-secrets`.

code

yaml · 6 lines
yaml
encryptionsalt: v1:8mQ2Rk1n0aY=:v1:2rN5b7Qk9pQ0nU1s:0Cq7xX2mB4vT9dJ1kR8sW3
config:
  aws:region: eu-west-1
  myapp:dbUser: app
  myapp:dbPassword:
    secure: v1:kR3nQ8pL:9sW2mT7xB4vC1dJ6hY0zA5qN8rE3uK

go deeper

for a junior

Know that pulumi config set --secret encrypts the value into Pulumi.<stack>.yaml so the file can be committed, and that --show-secrets is what reveals it again. Say that a secret is encrypted, not merely masked.

for a middle

Explain propagation — a secret feeding a resource argument stays secret and is stored encrypted in the checkpoint — and name the secrets-provider choices: the Pulumi Cloud key, passphrase with PULUMI_CONFIG_PASSPHRASE, or a cloud KMS URL.

for a senior

Show how you'd operate this: which provider for which environment, how the passphrase or KMS grant reaches CI, why --show-secrets in a pipeline is a review item, and what you do when a value leaks despite all of it.

for a principal

Own the key-management boundary: who can decrypt a production stack, whether that right is auditable and revocable, and how the answer differs between a shared passphrase and an IAM-governed KMS key. Be ready to justify the blast radius you accepted.

## The problem Every infrastructure tool eventually has to hold a database password, an API token or a TLS key — first as an input you set, then as a value recorded in whatever the tool uses to remember what it built. Pulumi's position is that both places are encrypted by default rather than plaintext-with-a-warning, which is the main reason this topic comes up in interviews. ## Marking a value secret There are two entry points. From the CLI, `--secret` on config: ```bash pulumi config set --secret dbPassword 'hunter2' pulumi config set dbUser app # not secret ``` The resulting `Pulumi.dev.yaml` looks like: ```yaml encryptionsalt: v1:AbCdEf...:v1:... config: myapp:dbUser: app myapp:dbPassword: secure: v1:9zQ...:ciphertext... ``` Because only ciphertext lands in the file, the stack config file is committed to the repository like any other source. From inside the program, `pulumi.secret(value)` wraps a computed value, and reading a secret config key returns it already marked (the config API has explicit "secret" accessors for this). Either way, the engine now knows this particular value is sensitive. ## Secretness propagates This is the part candidates most often miss. Pulumi's values are `Output`s, and secretness travels along the dataflow: if a secret feeds an `apply`, a string interpolation over Outputs, or a resource argument, the result is secret too. Consequences: - In previews and `pulumi up` output, the value renders as `[secret]`. - In the **state checkpoint**, the property is stored encrypted, not in the clear. `pulumi stack export` emits a JSON document in which those properties are ciphertext. - Outputs exported from a secret are themselves secret, so `pulumi stack output dbPassword` refuses to print it unless you pass `--show-secrets`. The escape hatches are the obvious ones: if you pull the value out of the Output system into a plain string and log it, print it, or feed it to a provisioner-style command that echoes it, nothing protects you afterwards. Encryption at rest is not the same as never being observable. ## The secrets provider The secrets provider is the key material used to encrypt the stack's secrets. It is set when the stack is created: ```bash pulumi stack init prod --secrets-provider="awskms://alias/pulumi-prod?region=eu-west-1" pulumi stack init dev --secrets-provider=passphrase ``` The options in practice: - **Pulumi Cloud (default when logged into the service).** The service manages a per-stack key; access follows your Pulumi Cloud permissions, and nothing extra needs distributing to engineers or CI. - **`passphrase`.** A key derived from a passphrase you supply, with the salt recorded as `encryptionsalt` in `Pulumi.<stack>.yaml`. Every place that runs `pulumi` must have `PULUMI_CONFIG_PASSPHRASE` (or `PULUMI_CONFIG_PASSPHRASE_FILE`) set. Simple, and the usual choice for a self-managed backend — but the passphrase is a shared secret you now have to distribute and rotate, and if it is lost the stack's secrets are unrecoverable. - **A cloud KMS key** — `awskms://`, `gcpkms://`, `azurekeyvault://`, or `hashivault://`. Pulumi stores the provider URL and an encrypted data key in the stack file; the decryption right becomes an IAM/policy decision on that key rather than a string in someone's password manager. This is the strongest option for a team on a self-managed backend, because you can audit and revoke access to the key. A stack can move between providers later with `pulumi stack change-secrets-provider`, which re-encrypts the stack's secrets under the new key. ## Revealing values on purpose ```bash pulumi config get dbPassword --show-secrets pulumi stack output connectionString --show-secrets ``` Both exist because you sometimes genuinely need the value — but they are also how secrets end up in CI logs. Treat `--show-secrets` in a pipeline as something that needs a reason. ## Where this leaves you in an interview The crisp summary: *marking* is per-value and sticky, *encryption* covers both the config file and the state checkpoint, and the *secrets provider* is the pluggable key behind both. The follow-up worth anticipating is what happens when the key is lost or the value leaks — the ciphertext protects the file, but a value that was ever committed in the clear must be rotated at its source, because re-encrypting the config does nothing about copies already in the repository's history.

  • If a secret config value feeds a resource argument, is it still protected once the resource exists?
    Yes — secretness propagates through Outputs and `apply`, so the derived property is stored encrypted in the state checkpoint and rendered as `[secret]` in previews. Protection ends the moment you take the value out of the Output system and log or print it, or pass it to a command that echoes it, so the risk moves from the file to your own code.
  • What happens if the passphrase for a stack using the passphrase secrets provider is lost?
    There is no recovery path: the config secrets and the encrypted properties in state cannot be decrypted, and `pulumi up` cannot run against that stack. In practice you re-create the stack and re-import or rebuild its resources. That risk is the argument for a cloud KMS provider, where the key lives in a service with IAM and backups instead of in a shared string.
  • Is a stack config file with secret values safe to commit?
    Yes, that is the design — secret values appear only as `secure:` ciphertext, and the passphrase provider stores just a salt. The danger is a value set *without* `--secret`, which is written in the clear; reviewing stack-config diffs for keys that should have been secret is worth a pre-commit check.

saying these in an interview costs you the question

  • Thinks --secret only hides the value in CLI output
  • Claims Pulumi stores state secrets in plaintext like some other tools
  • Assumes secretness is lost once the value enters a resource
  • Commits a password without --secret because the file 'looks encrypted'
  • Believes the secrets provider can be changed only by recreating the stack

context