The backend {} sub-block, declared inside ubx {}, controls where Pulumi stores stack state. When present, it overrides both the PULUMI_BACKEND_URL environment variable and the default local file backend for every ubx ship run in that workspace.
backend only affects CLI behavior — no Python code is generated from it.
Syntax
ubx {
backend s3 {
bucket = "my-infra-state"
key = "networking/prod"
}
}
The backend type (s3, gcs, azblob, local, pulumi) is a bare identifier, not a quoted string. It follows the backend keyword and precedes the opening {.
Only one backend {} block is allowed inside a single ubx {} block. A duplicate produces a hard parse error.
Backend types
local
Stores state in a directory on the local filesystem.
ubx {
backend local {
path = "/var/state/my-project"
}
}
| Attribute | Required | Description |
|---|
path | yes | Absolute path, or a relative path resolved from the workspace directory |
Relative paths are resolved from the directory containing the .xcl files. The produced URL is file://<path>.
Stores state in an AWS S3 bucket.
ubx {
backend s3 {
bucket = "my-infra-state"
key = "networking/prod"
}
}
| Attribute | Required | Description |
|---|
bucket | yes | S3 bucket name |
key | yes | Key prefix within the bucket |
Produced URL: s3://<bucket>/<key>
gcs
Stores state in a Google Cloud Storage bucket.
ubx {
backend gcs {
bucket = "my-state-bucket"
prefix = "networking" // optional
}
}
| Attribute | Required | Description |
|---|
bucket | yes | GCS bucket name |
prefix | no | Key prefix within the bucket |
Produced URL: gs://<bucket> or gs://<bucket>/<prefix> when prefix is set.
The URL scheme is gs:// — not gcs://. This matches what Pulumi expects for the GCS backend.
azblob
Stores state in an Azure Blob Storage container.
ubx {
backend azblob {
container = "my-state-container"
path = "infra/prod" // optional
}
}
| Attribute | Required | Description |
|---|
container | yes | Azure Blob container name |
path | no | Path prefix within the container |
Produced URL: azblob://<container> or azblob://<container>/<path> when path is set.
pulumi
Uses Pulumi Cloud as the state backend.
ubx {
backend pulumi {
org = "my-org" // optional — omit to use the default account
}
}
| Attribute | Required | Description |
|---|
org | no | Pulumi organization name |
Produced URL: https://api.pulumi.com or https://api.pulumi.com/api/<org> when org is set.
Encryption sub-block
The optional encryption {} sub-block configures a secrets provider for stack secrets. When present, ubx passes the corresponding --secrets-provider flag to Pulumi during stack operations.
ubx {
backend s3 {
bucket = "my-infra-state"
key = "networking/prod"
encryption {
provider = "awskms"
key = "alias/pulumi-secrets"
region = "us-east-1"
}
}
}
Only one encryption {} block is allowed inside backend {}. A duplicate produces a hard parse error. Unknown attribute names produce a hard parse error.
Encryption fields
| Attribute | Required for | Optional for | Description |
|---|
provider | all | — | Encryption provider name (see below) |
key | awskms, azurekeyvault, gcpkms | — | KMS key identifier or URI |
region | — | awskms | AWS region override for the KMS key |
env | — | passphrase | Environment variable name holding the passphrase |
awskms
encryption {
provider = "awskms"
key = "alias/pulumi-secrets"
region = "us-east-1" // optional
}
key is required. It may be a key alias (alias/name), a key ARN, or any form accepted by the AWS KMS API. region is optional — when omitted, the ambient AWS region is used.
Produces --secrets-provider awskms://alias/pulumi-secrets or awskms://alias/pulumi-secrets?region=us-east-1 when region is set.
azurekeyvault
encryption {
provider = "azurekeyvault"
key = "https://myvault.vault.azure.net/keys/mykey"
}
key is required. It is the full Azure Key Vault key URI. The leading https:// is stripped when forming the secrets provider string.
Produces --secrets-provider azurekeyvault://myvault.vault.azure.net/keys/mykey.
gcpkms
encryption {
provider = "gcpkms"
key = "projects/my-project/locations/global/keyRings/my-ring/cryptoKeys/my-key"
}
key is required. It is the full GCP KMS resource path.
Produces --secrets-provider gcpkms://projects/my-project/locations/global/keyRings/my-ring/cryptoKeys/my-key.
passphrase
encryption {
provider = "passphrase"
env = "MY_PULUMI_PASSPHRASE" // optional
}
No key is required. env names the environment variable that holds the passphrase. When env is omitted, Pulumi looks for PULUMI_CONFIG_PASSPHRASE by default.
Produces --secrets-provider passphrase.
Cross-provider warnings
The typechecker emits warnings for attributes that do not apply to the chosen provider:
| Situation | Warning |
|---|
env set with a KMS provider | 'env' is only meaningful for provider = "passphrase" |
region set with azurekeyvault or gcpkms | 'region' is only meaningful for provider = "awskms" |
key set with passphrase | 'key' is not used for provider = "passphrase" |
These are warnings — the stack still compiles and the extraneous attributes are silently ignored at runtime.
Priority order
When ubx ship resolves the backend URL, it uses the following priority:
backend {} in ubx {} — explicit declaration in source (highest priority)
PULUMI_BACKEND_URL environment variable — runtime override
- Default —
file://<workspace-dir>/.ubx/state/ (lowest priority)
// This declaration wins over any PULUMI_BACKEND_URL in the environment.
ubx {
backend s3 {
bucket = "my-infra-state"
key = "networking/prod"
}
}
Secrets provider wiring
When an encryption {} block is present, ubx ship extracts the secrets provider string and passes it to the Pulumi plugin. The plugin uses it when initializing a new stack (pulumi stack init --secrets-provider <value>).
For an already-initialized stack, the secrets provider is stored in the stack’s state file. Changing the provider declaration after a stack is initialized requires a migration:
pulumi stack change-secrets-provider "awskms://alias/new-key?region=us-east-1"
Run this command from the generated .ubx/ directory, or with the Pulumi stack set to the correct stack name.
Secrets provider drift
If the encryption {} block in source declares a different provider than what the existing stack was initialized with, ubx ship emits a warning:
warning: secrets provider in source (awskms://...) differs from stack's current provider
run: pulumi stack change-secrets-provider "awskms://..." to migrate
Full drift detection compares the declared provider against the current stack state. The migration command is pulumi stack change-secrets-provider "<new-provider-string>".
Drift detection is a best-effort warning, not a hard error. The deploy proceeds regardless. If the providers genuinely differ at apply time, Pulumi will fail with a decryption error.
No codegen emitted
backend {} affects only ubx ship behavior. Nothing in __main__.py changes based on the backend declaration — no import, no variable, no comment is emitted. Pulumi receives the backend URL and secrets provider string through the plugin RPC, not through the generated Python program.
All attributes must be string literals
All attributes inside backend {} (and encryption {}) must be string literals. Expressions, input references, and locals references are not allowed:
// Valid
backend s3 {
bucket = "my-bucket"
}
// Error — expressions not allowed in backend attributes
backend s3 {
bucket = input.cfg.bucket_name // ✗ error: must be a string literal
}
This restriction exists because backend configuration must be resolved at compile time, before any stack inputs are available.
Error conditions
Unknown backend type
unknown backend type "firebase" — valid types: local, s3, gcs, azblob, pulumi
Non-string-literal attribute
backend "s3": attribute "bucket" must be a string literal — expressions and variable references are not allowed
Missing encryption provider
encryption block requires a 'provider' attribute — valid: awskms, azurekeyvault, gcpkms, passphrase
Unknown encryption provider
unknown encryption provider "hashivault" — valid: awskms, azurekeyvault, gcpkms, passphrase
Missing key for KMS provider
encryption provider "awskms" requires a 'key' attribute
Duplicate backend block (parse error)
duplicate backend block — only one backend {} is allowed inside ubx {}
Duplicate encryption block (parse error)
duplicate encryption block — only one encryption {} is allowed inside backend {}