Appearance
Deployment overview
Operational reference, not a decision record — nothing here needs an ADR because it implements choices already settled elsewhere (no accounts, no auth, deployed HOST never mounts Curation). Where this page and a repo's own deploy/RUNBOOK.md disagree, the RUNBOOK wins — see Backend deployment.
Topology
GitHub Cloudflare DO droplet (Docker Compose)
frontend ──CI──► Pages ────────► app.chemassist-occ.com ──credentialed-free JSON fetch──┐
│
api ──CI──► GHCR image ──SSH deploy──────────────────────────────┐ │
api.chemassist-occ.com ──► cloudflared ──► nginx ──► api:3000
api ──► postgres:5432
api ──► redis:6379Two independently-deployed repos, each a gitlink under this workspace's app/ (ADR-0063):
| Component | Repo | Hosting |
|---|---|---|
| Frontend | app/chemical-safety-assistant-frontend | Cloudflare Pages, static build |
| API | app/chemical-safety-assistant-api | Docker Compose on one DigitalOcean droplet, reached through a Cloudflare Tunnel |
Why no MinIO in production
Curation and Upload — the only modules that touch object storage — are mounted only when the API process binds a loopback address (HOST=127.0.0.1), never when deployed. The production droplet leaves HOST unset (0.0.0.0), so those routes don't exist there and the droplet never needs MinIO configured. A curator runs the API on their own machine, against their own Postgres and MinIO, per the loopback ruling recorded as ADR-0048. This deploy doesn't touch that workflow.
Why no auth-driven subdomain cookie constraint
The sibling work-permit project pins all its frontends and its API to one apex domain because its session cookie is Domain=.<domain>. This product has no accounts (ADR — the no-accounts ruling, D-60): there's no session cookie, so the frontend and API don't need to share a domain suffix for auth to work. api.chemassist-occ.com and app.chemassist-occ.com is a convention here, not a requirement — CORS (CORS_ORIGIN on the API) is what actually has to agree with the frontend's real origin.
Deploy order for a from-scratch environment
- Droplet + Postgres + Redis + API, per Backend deployment — the frontend needs a live API URL to build against.
- Cloudflare Tunnel hostname for the API, so
VITE_APP_API_URLpoints at something real. - Frontend, per Frontend deployment.
What's NOT here
- No staging/alpha environment — one droplet, one Cloudflare Pages project,
mainis production. Add environments later by branching the compose project name and the Pages branch, the same waywork-permit'sdeploy.shdid, if this product needs them. - No horizontal scaling of the API — single replica by design (migrations run at container start; two replicas would race).
- No managed Postgres/Redis — both run as containers on the same droplet as the API. Fine at this scale (one plant, prototype); revisit if load grows.