Skip to main content
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"
    }
}
AttributeRequiredDescription
pathyesAbsolute 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>.

s3

Stores state in an AWS S3 bucket.
ubx {
    backend s3 {
        bucket = "my-infra-state"
        key    = "networking/prod"
    }
}
AttributeRequiredDescription
bucketyesS3 bucket name
keyyesKey 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
    }
}
AttributeRequiredDescription
bucketyesGCS bucket name
prefixnoKey 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
    }
}
AttributeRequiredDescription
containeryesAzure Blob container name
pathnoPath 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
    }
}
AttributeRequiredDescription
orgnoPulumi 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

AttributeRequired forOptional forDescription
providerallEncryption provider name (see below)
keyawskms, azurekeyvault, gcpkmsKMS key identifier or URI
regionawskmsAWS region override for the KMS key
envpassphraseEnvironment 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:
SituationWarning
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:
  1. backend {} in ubx {} — explicit declaration in source (highest priority)
  2. PULUMI_BACKEND_URL environment variable — runtime override
  3. Defaultfile://<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 {}