Skip to main content
Pulumi state files contain resource attributes — including the values of sensitive outputs marked pulumi.secret(). By default, these sensitive values are encrypted with a Pulumi-managed key when using Pulumi Cloud, but when using a self-managed backend like S3, the encryption is your responsibility. Without the encryption sub-block, sensitive values in state are stored as base64-encoded strings that anyone with S3 access can decode. The encryption sub-block inside backend tells ubx which secrets provider to use when initialising the Pulumi stack — either an AWS KMS key (rotatable, access-controlled, audited via CloudTrail) or a passphrase (simpler, portable, but requires secure distribution). One backend block, one encryption configuration, and your state secrets are protected at rest.

What you’ll learn

  • How encryption { provider = "awskms" } encrypts state secrets with a KMS key
  • How encryption { provider = "passphrase" } uses an environment variable passphrase
  • Why only one backend block is allowed per compile unit

Why this matters

State encryption matters because state contains secrets. An RDS password marked pulumi.secret() is encrypted in state — but only if the stack was initialised with a secrets provider. Without encryption, sensitive state values are base64-encoded, not encrypted. Anyone who can read your S3 bucket can decode them.

The source code

unit "aws_s3_bucket_v2" "assets" {
  bucket = "myapp-${input.env}-assets"
}

How it works

1

encryption configures the Pulumi secrets provider on stack init

The encryption sub-block translates to a --secrets-provider flag on pulumi stack init. For KMS: --secrets-provider=awskms://arn:aws:kms:.... For passphrase: --secrets-provider=passphrase with PULUMI_CONFIG_PASSPHRASE set in the environment.
2

Sensitive state values are encrypted with the specified key

Any output or config value wrapped in pulumi.secret() (or produced by an ephemeral = true input) is encrypted using the configured KMS key before being written to the S3 state file. The KMS key ID is stored in the state file metadata so future reads know which key to use for decryption.
3

Only one backend block is allowed per compile unit

Two backend blocks in the same project is a compile error — ubx enforces single-backend semantics. The passphrase variant is documented as a comment to show the alternative syntax without requiring a second block.

What ubx generates

Like the plain backend block, encryption produces no TypeScript/Python/Go code. It configures the Pulumi CLI before the generated program runs. The secrets provider is set once at stack initialisation and stored in the stack’s metadata.
# ubx translates the encryption block to this Pulumi CLI invocation:
pulumi stack init --secrets-provider="awskms://arn:aws:kms:us-east-1:123456789012:key/mrk-abc123"

# For passphrase:
PULUMI_CONFIG_PASSPHRASE=secret pulumi stack init --secrets-provider="passphrase"

Common mistakes

Two backend blocks in the same project is a hard compile error — ✗ multiple "backend" blocks found — only one is allowed per project. If you want to show the passphrase variant alongside the KMS variant (as this example does), put the second one in a comment. This is by design: a stack can’t have two different backends simultaneously.
Use KMS Multi-Region Keys (mrk- prefix) for stacks that may be recovered or replicated across regions. A single-region KMS key becomes inaccessible if the key’s region is unavailable — Multi-Region Keys can be replicated to a second region as a disaster recovery measure.

Run it

ubx validate              # syntax check — no KMS access needed
ubx plan                  # requires AWS credentials
ubx apply                 # initialises stack with KMS encryption on first run

What you learned

encryption { provider = "awskms" } encrypts sensitive state values with a KMS key
encryption { provider = "passphrase" } uses an environment variable as the encryption passphrase
Only one backend block is allowed per compile unit — a second block is a compile error

Next steps

Code hierarchy

Share backend and provider config across multiple stacks

backend block reference

Full backend and encryption sub-block syntax