Securing Sensitive Data with Pulumi Secrets
Learn how to encrypt API keys and passwords with Pulumi Secrets, access them safely in code, verify masking in logs, and avoid key-loss pitfalls.
01 Aug 2025, 18:46 UTC

Hardcoding API keys, database passwords, or certificates in your infrastructure-as-code files creates a significant security vulnerability. Even if your repository is private, those values end up in plain text inside the Pulumi state file, which tracks the metadata of your resources. If that state file leaks, your infrastructure credentials go with it.
Pulumi Secrets solve this by encrypting sensitive values before they reach the state file and masking them in execution logs. This guide covers how to configure, consume, and verify secrets in a Pulumi stack, plus the risks you need to plan for.
Prerequisites
- Pulumi CLI installed and logged in (via the Pulumi Service or a self-managed backend).
- An initialized Pulumi project with at least one stack (
pulumi stack init dev). - An encryption provider. By default, the Pulumi Service manages the encryption key. For production, you can configure a cloud KMS provider (AWS KMS, Azure Key Vault, or Google Cloud KMS) when creating the stack, e.g.
pulumi stack init --secrets-provider="awskms://alias/my-pulumi-key".
Storing an Encrypted Secret
Run this command from your project directory. It requires no special permissions beyond stack access; replace the key and value with your own:
pulumi config set --secret db-password "my-super-secret-password"Verify the result by opening the stack configuration file (e.g. Pulumi.dev.yaml). The value should appear as ciphertext, not plain text:
config:
my-project:db-password:
secure: v1:ABC123...encrypted-string...xyzIf you see the plain password instead, the --secret flag was not applied — re-run the command with the flag.
Accessing Secrets in Code
Pulumi decrypts secrets automatically at runtime and wraps them in a secret Output, which keeps the value masked in logs and diffs. The key point: use the secret-aware SDK method, not the plain one.
Example: TypeScript
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const config = new pulumi.Config();
// Returns Output<string> marked as secret
const dbPassword = config.requireSecret("db-password");
const database = new aws.rds.Instance("my-db", {
engine: "postgres",
instanceClass: "db.t3.micro",
username: "appadmin",
password: dbPassword, // secret status is preserved
skipFinalSnapshot: true,
});Using requireSecret (or getSecret) matters: a plain require treats the value as a normal string, so it could appear in logs if printed during debugging. Equivalent methods exist in Python (config.require_secret), Go (RequireSecret), and C# (RequireSecret).
Verifying the Setup
- Masking check: Run
pulumi uporpulumi preview. Properties derived from the secret should display as[secret]in the diff output. - Decryption check: Run
pulumi config get db-password. The plain value is shown only if your identity has permission to use the stack's encryption key. - State inspection: Run
pulumi stack exportand search the output for your secret value. It should not appear in plain text anywhere in the state.
Limitations and Risks
- Key loss is unrecoverable: With a custom KMS provider, deleting the key or losing IAM access means existing secrets in state can never be decrypted. You would have to re-set every secret and replace affected resources.
- CI/CD permissions: Your pipeline identity needs decrypt permission on the KMS key (or Pulumi Service access). A common failure is a pipeline that can deploy but cannot decrypt, producing errors at preview time.
- Bypass risk: Hardcoding a secret in source code skips encryption entirely, even if the same value also exists in config. Keep all sensitive values in the config system.
- Config file in version control: Committing
Pulumi.dev.yamlis safe for encrypted values, but avoid committing stacks that use a passphrase provider with the passphrase stored nearby.
If you need to rotate a secret, simply run pulumi config set --secret db-password "new-value" again and run pulumi up to propagate the change to dependent resources.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.