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

# Deployment

> Ship each part to production — keeping secrets on the server.

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

## 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_*`:

```bash theme={null}
docker build --build-arg VITE_API_URL=https://api.example.com \
             --build-arg VITE_SITE_URL=https://example.com \
             --build-arg VITE_BRAND="Your product" -t shipwide-admin .
docker run -p 8080:80 shipwide-admin
```

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