Skip to main content
By default, ubx uses a local file backend — state is stored at .ubx/state on your machine, invisible to teammates and lost if you lose the machine. For any real team or production workload, you need a remote backend. S3 is the most common choice for AWS-focused teams: it’s durable, versioned, access-controlled, and already part of your infrastructure. The backend "s3" block configures Pulumi’s state storage to use an S3 bucket, with a key path that can include ${stack} interpolation so each stack (dev, staging, prod) stores its state at a separate, non-overlapping path. Every team member who runs ubx apply reads and writes the same state file — no more “works on my machine” state divergence.

What you’ll learn

  • How backend "s3" configures Pulumi state storage in S3
  • How ${stack} interpolation creates per-stack state isolation
  • What happens at validate time vs apply time for backend configuration

Why this matters

Remote state is not optional for teams or production. Without it, two team members can’t apply the same stack without corrupting each other’s state. With S3 backend, state is a shared, durable, versioned artifact — team members work from the same source of truth.

The source code

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

How it works

1

ubx translates the backend block to a Pulumi login command

When you run ubx apply, ubx calls pulumi login s3://my-ubx-state before executing the Pulumi program. The key path is passed as the state file location. The ${stack} variable resolves to the current stack name (dev, staging, or prod).
2

Each stack gets a separate, non-overlapping state path

key = "myapp/${stack}/pulumi.json" means dev and prod never share a state file. Running ubx apply --env prod writes to myapp/prod/pulumi.json; running ubx apply (default env) writes to myapp/dev/pulumi.json. No risk of one stack overwriting another’s state.
3

ubx validate checks syntax only — no S3 access required

The backend block is syntax-validated at compile time. The S3 bucket is not contacted during ubx validate or ubx plan — only during ubx apply when Pulumi actually needs to read and write state. This means validate and plan work without S3 credentials.

What ubx generates

backend blocks produce no TypeScript/Python/Go code. They configure the Pulumi CLI’s login target before the generated program runs. The generated program itself is identical regardless of backend — only the state storage location changes.
index.ts
// Generated by ubx — do not edit
// (backend configuration is handled by ubx before running this program)
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const config = new pulumi.Config();
const env = config.get("env") || "dev";

const assets = new aws.s3.BucketV2("assets", {
    bucket: `myapp-${env}-assets`,
});

export const bucketName = assets.bucket;

Common mistakes

The S3 bucket (my-ubx-state) must exist before the first ubx apply. ubx does not create the state bucket — it assumes the bucket already exists. Create the state bucket as a one-time manual step or via a bootstrap stack before onboarding the rest of your infrastructure to ubx.
Enable S3 versioning on your state bucket. Pulumi state files are written atomically, but versioning gives you a recovery path if state is accidentally corrupted or deleted. Combined with S3 MFA delete, versioned state buckets are effectively immutable from the perspective of accidental deletion.

Run it

ubx validate             # syntax check — no S3 access needed
ubx plan                 # requires AWS credentials (reads state from S3)
ubx apply                # writes state to s3://my-ubx-state/myapp/dev/pulumi.json
ubx apply --env prod     # writes to s3://my-ubx-state/myapp/prod/pulumi.json

What you learned

backend "s3" stores Pulumi state in S3 — accessible to the whole team
${stack} in the key path creates per-stack state isolation
The S3 bucket must exist before the first apply; ubx validate doesn’t contact S3

Next steps

Backend encryption with KMS

Encrypt state secrets with AWS KMS

backend block reference

Full backend block syntax for S3, GCS, and Azure Blob