> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ubiquex.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Backend — S3 State Storage

> Store Pulumi state in S3 with per-stack path isolation and team-accessible state.

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

<Info>
  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.
</Info>

***

## The source code

<CodeGroup>
  ```hcl main.iac theme={"theme":"css-variables"}
  unit "aws_s3_bucket_v2" "assets" {
    bucket = "myapp-${input.env}-assets"
  }
  ```

  ```hcl inputs.iac theme={"theme":"css-variables"}
  input "stack" {
    type    = "string"
    default = "dev"
  }

  input "env" {
    type    = "string"
    default = "dev"
  }
  ```

  ```hcl outputs.iac theme={"theme":"css-variables"}
  output "bucket_name" {
    value = unit.aws_s3_bucket_v2.assets.bucket
  }
  ```

  ```hcl ubx.iac theme={"theme":"css-variables"}
  ubx {
    name    = "backend-s3-state"
    runtime = "typescript"
    stacks  = ["dev", "staging", "prod"]
  }

  # backend "s3" stores Pulumi state in an S3 bucket.
  # key uses ${stack} interpolation — each stack gets its own state file path.
  # dev   → s3://my-ubx-state/myapp/dev/pulumi.json
  # staging → s3://my-ubx-state/myapp/staging/pulumi.json
  # prod  → s3://my-ubx-state/myapp/prod/pulumi.json
  backend "s3" {
    bucket = "my-ubx-state"
    region = "us-east-1"
    key    = "myapp/${stack}/pulumi.json"
  }
  ```
</CodeGroup>

***

## How it works

<Steps>
  <Step title="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`).
  </Step>

  <Step title="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.
  </Step>

  <Step title="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.
  </Step>
</Steps>

***

## What ubx generates

<Note>
  `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.
</Note>

```typescript index.ts theme={"theme":"css-variables"}
// 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

<Warning>
  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.
</Warning>

<Tip>
  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.
</Tip>

***

## Run it

```bash theme={"theme":"css-variables"}
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

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

***

## Next steps

<CardGroup cols={2}>
  <Card title="Backend encryption with KMS" href="/v1/tutorials/40-backend-encryption-kms">
    Encrypt state secrets with AWS KMS
  </Card>

  <Card title="backend block reference" href="/v1/language/backend">
    Full backend block syntax for S3, GCS, and Azure Blob
  </Card>
</CardGroup>

***

<Info>
  Full runnable example: [github.com/ubiquex/ubx-examples/39-backend-s3-state](https://github.com/ubiquex/ubx-examples/tree/main/39-backend-s3-state)
</Info>
