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
iadonly. 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 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
# 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 > .envFly.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:
docker compose -f apps/strait/docker-compose.yml up -dFly.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
deploy/
└── sequin/
└── fly.toml # Fly config for Sequin CDCDeploying 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).
# From apps/strait/ — runs vet, lint, build, test before deploying
make deploy
# Or from repo root
bun run deployThe 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.
# From repo root
bun run deploy:sequin
# Or directly
cd deploy/sequin && fly deploy -a strait-sequinSequin 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:
- Code is pushed to
master - Changes are in
apps/strait/** - All required workflows pass (
Test,Lint,Security)
Required GitHub secret: FLY_API_TOKEN — generate a deploy token scoped to the app:
fly tokens create deploy -a straitProduction 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 for the full reference.
Scaling Strategy
For high-traffic production environments, it is recommended to separate the API and worker processes:
- Scale API: Increase the number of instances running in
apimode to handle more concurrent HTTP requests. - Scale Workers: Increase the number of instances running in
workermode to process more jobs from the queue. Since workers useSKIP 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.
Always use a process manager or orchestrator (like Kubernetes or Fly.io) that respects SIGTERM signals to ensure no jobs are abruptly interrupted.
Quick Reference
# 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