Deploy Basecoat with SST and Cloudflare
Basecoat deploys with SST targeting Cloudflare. The web app, LiveStore sync worker, Durable Object backend, and media storage should all be configured around that platform.
Run the app and sync backend in separate terminals:
bun run livestore:sync
bun run dev
The LiveStore sync worker uses apps/livestore-sync/wrangler.jsonc locally and the app
reads VITE_LIVESTORE_SYNC_URL.
Required production values include:
BETTER_AUTH_SECRET=your-production-secret
BETTER_AUTH_URL=https://basecoat.app
ONE_SERVER_URL=https://basecoat.app
CLOUDFLARE_API_TOKEN=your-cloudflare-api-token
CLOUDFLARE_R2_ENDPOINT=...
CLOUDFLARE_R2_ACCESS_KEY=...
CLOUDFLARE_R2_SECRET_KEY=...
CLOUDFLARE_R2_PUBLIC_URL=...
LIVESTORE_SYNC_URL=wss://livestore.basecoat.app
VITE_LIVESTORE_SYNC_URL=wss://livestore.basecoat.app
WEB_DOMAIN=basecoat.app
The LiveStore sync backend runs as a Cloudflare Worker backed by a Durable Object. SST owns the production deployment, while Wrangler supports local development.
Uploaded images and videos are stored in R2 or another S3-compatible object store. LiveStore events carry metadata and storage references, not media binaries.
Product semver is tracked in two deployable workspaces, kept in sync by Release Please:
| Package | Path | Bumped by |
|---|---|---|
| App (web + mobile) | apps/basecoat/package.json version | Release Please |
| LiveStore sync worker | apps/livestore-sync/package.json version | Release Please (linked to app) |
Both share CHANGELOG.md at the repo root. The linked-versions plugin ensures a single
Release PR bumps both to the same semver.
Native store / OTA targeting uses a separate field on the app package only:
| Field | Bumped by | Used for |
|---|---|---|
nativeVersion in apps/basecoat/package.json | You, when native fingerprint changes | runtimeVersion, OTA targeting, store binary compatibility |
Release Please only bumps version on the app and sync worker. OTA bundles target nativeVersion via
Hot Updater’s appVersion strategy (runtimeVersion in app.config.ts).
When adding native modules, plugins, or native config, bump native version:
bun tko release/bump-native-version
CI fails on main if the Expo fingerprint changed but nativeVersion did not.
CI (ci.yml) runs on every push/PR: check, lint, format, unit tests.
Product versioning (release-please.yml) runs on every push to main. It opens or
updates a Release Please PR that bumps version in apps/basecoat/package.json and
apps/livestore-sync/package.json, updates CHANGELOG.md, and updates
.release-please-manifest.json from Conventional Commits
(feat: → minor, fix: → patch, feat!: / BREAKING CHANGE: → major).
Merge that Release PR when you want a product version cut. This pipeline does not create GitHub Releases — changelog and semver live in the repo only.
Deploy pipelines (separate lifecycles, triggered manually or after CI on main):
| Workflow | Purpose | Skip when |
|---|---|---|
release-please.yml | Product semver + CHANGELOG.md | — |
release-web.yml | One web + API production build | Version/changelog-only merge |
release-livestore-sync.yml | LiveStore sync Worker (path-filtered push) | No sync paths changed |
release-mobile.yml | OTA or store builds (fingerprint classify) | Version/changelog-only merge |
Commit scopes (feat(paint), fix(livestore-sync)) are for CHANGELOG readability
only. They do not drive semver bumps or deploy routing. Deploy routing uses changed
paths and Expo fingerprint diff.
release-mobile.yml compares @expo/fingerprint at the merge commit vs its parent.
production channel (matches
APP_VARIANT=production in store builds).ios-cd and/or android-cd store builds (requires
nativeVersion bump in the same merge).Manual overrides: dispatch release-mobile with force_strategy (ota / native), channel,
rollout percentage, or force-update flag.
Preview store builds on PRs still use the native: commit/title gate.
Use scopes for humans and CHANGELOG grouping — not for automation.
Deployable: web, livestore-sync, mobile, hot-update, ci, release-please, deps
Domain: collection, model, paint, paint-scheme, mixes, session, dashboard, auth, profile, settings, notifications, interface, livestore
Cross-platform features: scope by primary intent (feat(paint): …). Put platform-specific
work in separate commits when deploy paths differ (feat(mobile): add expo-camera).
Mention Requires-Native-Build: yes in the commit body when relevant.