Skip to content

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:6379

Two independently-deployed repos, each a gitlink under this workspace's app/ (ADR-0063):

ComponentRepoHosting
Frontendapp/chemical-safety-assistant-frontendCloudflare Pages, static build
APIapp/chemical-safety-assistant-apiDocker 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.

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

  1. Droplet + Postgres + Redis + API, per Backend deployment — the frontend needs a live API URL to build against.
  2. Cloudflare Tunnel hostname for the API, so VITE_APP_API_URL points at something real.
  3. Frontend, per Frontend deployment.

What's NOT here

  • No staging/alpha environment — one droplet, one Cloudflare Pages project, main is production. Add environments later by branching the compose project name and the Pages branch, the same way work-permit's deploy.sh did, 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.