Golden rule: secrets stay on the server. Fill the CHANGE-ME placeholders in the API’s
env.prod there, never commit them, and never put a secret in a VITE_* / PUBLIC_* var
(those are inlined into the shipped bundle).
Backend (shipwide_api)
- Docker: the container boots
migrate → seed → serve. Migrations + the idempotent seed run
automatically. Provide a managed Postgres 17 + Redis 7 (or run them alongside).
- Purge cron (required): run daily —
node dist/jobs/purge.js (compiled path; tsx is
dev-only). It’s the physical half of account deletion + support-ticket retention; idempotent.
- Email: point
SMTP_* at a transactional provider (e.g. Postmark). Set SPF, DKIM, DMARC
on the sending domain — with passwordless auth, a code in spam means nobody can sign in.
- CORS:
CORS_ORIGINS = your exact web origin(s). Native schemes (capacitor://localhost,
tauri://localhost) and the admin’s ADMIN_URL are always allowed.
- Secrets to set:
DATABASE_URL, REDIS_URL, JWT_SECRET (≥32), SMTP_*, ADMIN_*, and
any GOOGLE_/APPLE_/REVENUECAT/ONESIGNAL integration you enable.
App (shipwide_app)
- Web / PWA:
npm run build → static output (keeps pre-compressed .gz/.br assets). Serve
from any static host / CDN. Its origin must be in the API’s CORS_ORIGINS.
- iOS / Android:
npm run build:native (sets SHIPWIDE_NATIVE=1 so compressed assets aren’t
bundled into the WebView), then open the committed ios/ / android/ projects in Xcode /
Android Studio to archive and submit.
- Desktop: Tauri bundles from
src-tauri/ (Rust stable). Icons via npx tauri icon.
Admin (shipwide_admin)
Docker + nginx, built with --build-arg VITE_*:
nginx serves the SPA (fallback to index.html, hashed assets cached forever / index.html
never, noindex + DENY). Outside the image: (1) point ADMIN_URL in the API env at this
origin or CORS refuses it; (2) put a network gate in front (Cloudflare Access / IP allowlist)
— it reads everyone’s data and needs no public reachability.
Website (shipwide_website)
npm run switch_to_prod (set your domain first), then npm run build
(astro-i18next generate + astro check + astro build) → static output; serve from any static
host / CDN. PUBLIC_* are inlined at build. If the support form posts to your API, set
PUBLIC_SUPPORT_ENDPOINT and add the site origin to the API’s CORS_ORIGINS.