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

# Deployment

> Guide for deploying and scaling Strait in production.

Strait is designed as a single Go binary that can be deployed in various environments, from local development with Docker Compose to production on Fly.io or Kubernetes.

## Architecture Overview

Production deployment consists of the following services:

| Service                 | Platform                 | Region(s)           | Purpose                            |
| :---------------------- | :----------------------- | :------------------ | :--------------------------------- |
| **Strait** (Go backend) | Fly.io (`strait`)        | `iad`, `lhr`, `gru` | API server + worker                |
| **Sequin** (CDC)        | Fly.io (`strait-sequin`) | `iad`               | Postgres WAL → Redis pub/sub       |
| **PostgreSQL**          | PlanetScale              | `us-east-1`         | Primary data store                 |
| **Redis**               | Upstash                  | —                   | Pub/sub, SSE streaming, CDC events |
| **Frontend**            | —                        | —                   | `apps/app` (Vite/React)            |

### Region Strategy

* **Primary region**: `iad` (Ashburn, Virginia) — co-located with the database for lowest latency.
* **Edge regions**: `lhr` (London), `gru` (São Paulo) — for faster API responses globally.
* **Sequin**: Single instance in `iad` only. CDC requires a persistent WAL connection and must run next to the database. Multiple instances would cause duplicate event processing.

## Secrets Management

All environment variables are managed via [Doppler](https://doppler.com) under the `strait` project with three configs:

| Config | Environment | Notes                          |
| :----- | :---------- | :----------------------------- |
| `dev`  | Development | Local defaults, localhost URLs |
| `stg`  | Staging     | Shared infra with production   |
| `prd`  | Production  | Production secrets and URLs    |

### Local Development

```bash theme={null}
# Configure Doppler for this directory (one-time)
doppler setup  # select project: strait, config: dev

# Run any command with secrets injected
doppler run -- go run ./cmd/strait
doppler run -- bun run dev

# Or generate a local .env file
doppler secrets download --no-file --format env > .env
```

### Fly.io Integration

Secrets are synced from Doppler to Fly.io via the Doppler-Fly integration. When you update a secret in Doppler, it automatically propagates to Fly machines.

## Operation Modes

Strait can run in three different modes, controlled by the `--mode` flag or the `MODE` environment variable:

* **`api`**: Runs only the HTTP API server. Stateless and can be scaled horizontally.
* **`worker`**: Runs only the job execution engine. Scales based on queue depth.
* **`all`** (Default): Runs both the API and the worker in a single process. Ideal for small deployments or development.

## Infrastructure Requirements

Strait requires the following backing services:

* **PostgreSQL 18**: Primary data store and job queue (uses `SKIP LOCKED`).
* **Redis 8**: Used for pub/sub, SSE streaming, and CDC event publishing.
* **Sequin**: Required for Change Data Capture (CDC) from the Postgres WAL.

### Docker Compose (Development)

For local development, use `apps/strait/docker-compose.yml`:

```bash theme={null}
docker compose -f apps/strait/docker-compose.yml up -d
```

## Fly.io Deployment

### Project Structure

```
apps/strait/
├── Dockerfile      # Multi-stage Go build
├── fly.toml        # Fly config for the main app
├── Makefile        # Build, test, and deploy commands
└── .dockerignore

packages/deploy/
└── sequin/
    └── fly.toml    # Fly config for Sequin CDC
```

### Deploying the Main App

The Dockerfile uses a multi-stage build: Go compile on Alpine, then copy the binary to a minimal runtime image (\~16 MB).

```bash theme={null}
# From apps/strait/ — runs vet, lint, build, test before deploying
make deploy

# Or from repo root
bun run deploy
```

The deploy workflow runs preflight checks (`go vet`, `golangci-lint`, `go build`, `go test`) before pushing to Fly. If any check fails, the deploy is aborted.

### Deploying Sequin

Sequin uses the official `sequin/sequin:latest` Docker image — no build step needed.

```bash theme={null}
# From repo root
bun run deploy:sequin

# Or directly
cd packages/deploy/sequin && fly deploy -a strait-sequin
```

Sequin requires the following secrets set on Fly (managed via `fly secrets set`):

| Secret            | Description                    |
| :---------------- | :----------------------------- |
| `PG_HOSTNAME`     | PostgreSQL host                |
| `PG_PORT`         | PostgreSQL port                |
| `PG_USERNAME`     | PostgreSQL user                |
| `PG_PASSWORD`     | PostgreSQL password            |
| `PG_DATABASE`     | Database name                  |
| `PG_SSL`          | Enable SSL (`true`)            |
| `REDIS_URL`       | Redis connection string        |
| `SECRET_KEY_BASE` | Sequin internal encryption key |
| `VAULT_KEY`       | Sequin vault encryption key    |

### CI/CD

The GitHub Actions workflow (`.github/workflows/deploy.yml`) automatically deploys to Fly when:

1. Code is pushed to `master`
2. Changes are in `apps/strait/**`
3. All required workflows pass (`Test`, `Lint`, `Security`)

Required GitHub secret: `FLY_API_TOKEN` — generate a deploy token scoped to the app:

```bash theme={null}
fly tokens create deploy -a strait
```

## Production Configuration

### Required Environment Variables

| Variable                | Description                                                        |
| :---------------------- | :----------------------------------------------------------------- |
| `DATABASE_URL`          | Postgres connection string                                         |
| `REDIS_URL`             | Redis connection string                                            |
| `INTERNAL_SECRET`       | Secret for internal API auth (32+ chars, unique per environment)   |
| `JWT_SIGNING_KEY`       | Key for signing SDK run tokens (32+ chars, unique per environment) |
| `ENCRYPTION_KEY`        | Data encryption at rest (unique per environment)                   |
| `SECRET_ENCRYPTION_KEY` | Secret values encryption (unique per environment)                  |

See [Environment Variables](/configuration/environment-variables) for the full reference.

### Scaling Strategy

For high-traffic production environments, it is recommended to separate the API and worker processes:

1. **Scale API**: Increase the number of instances running in `api` mode to handle more concurrent HTTP requests.
2. **Scale Workers**: Increase the number of instances running in `worker` mode to process more jobs from the queue. Since workers use `SKIP LOCKED`, they can safely compete for jobs without double-processing.

## Graceful Shutdown

Strait implements graceful shutdown for both the API and the worker:

* **API**: Stops accepting new requests and waits for active requests to finish (up to the configured timeout).
* **Worker**: Stops dequeuing new jobs and waits for currently executing jobs to complete or reach a checkpoint before exiting.

<Tip>
  Always use a process manager or orchestrator (like Kubernetes or Fly.io) that respects SIGTERM signals to ensure no jobs are abruptly interrupted.
</Tip>

## Quick Reference

```bash theme={null}
# Deploy main app (with preflight checks)
make deploy                        # from apps/strait/
bun run deploy                     # from repo root

# Deploy Sequin
bun run deploy:sequin              # from repo root

# Check status
fly status -a strait
fly status -a strait-sequin

# View logs
fly logs -a strait
fly logs -a strait-sequin

# Manage secrets (via Doppler)
doppler secrets -p strait -c prd   # list production secrets
doppler secrets set KEY=value -p strait -c prd

# Local dev with Doppler
doppler run -- go run ./cmd/strait
doppler run -- bun run dev
```
