Deployment and operations

A high-level picture of what a running installation consists of and how it is kept healthy. It deliberately contains no hostnames beyond the public domains, no credentials and no server details.

Live addresses on helsen-med.com

Public addressWhat it serves
helsen-med.comThe storefront (Next.js): Arabic at /ar, English at /en. www.helsen-med.com redirects to it.
seller.helsen-med.comThe seller web console (static build).
delivery.helsen-med.comThe dispatch and delivery web console (static build), for dispatcher accounts.
api.helsen-med.comThe API at /api/v1, its Swagger UI at /docs, the health checks, realtime connections and product media.
docs.helsen-med.comThis documentation site: static files served by nginx.
Android APKsBuyer, seller and driver apps, distributed as files by the team (not on Google Play yet). They call api.helsen-med.com and pin its certificate's public key.
Not deployed: the admin console

The admin console is built but not published on any public address; that is pending a decision. Administrators use the API's Swagger UI meanwhile. See Live platform & first steps for what works today.

Details live in the ops runbook

Server layout, process management, TLS, backups, environment variables and access are documented in the operations runbook kept with the team — infra/deploy/README.md in the repository — not on this public site. Nothing here should be read as a deployment procedure.

What has to run

Production is one server. nginx terminates HTTPS with Let's Encrypt certificates that renew automatically and reuse the same key, so the apps' certificate pins stay valid across renewals. nginx also rate-limits per IP, more strictly on the sign-in routes, in front of the API's own per-IP limits. Everything else runs in Docker.

Process / serviceWhy
API (dist/main.js)Serves every client.
Worker (dist/worker.js)Drains the outbox and runs every scheduled sweep. Without it no notification is processed and no scheduled job runs — expired reservations, auto-completion, payouts and reconciliations included.
PostgreSQL 16System of record, including the outbox and job locks.
Redis 7Required by configuration; reserved for multi-instance features (presence, socket revocation).
Meilisearch 1.12Buyer product search.
SeaweedFS (S3-compatible)Object storage for product media, documents and proof-of-delivery photos. MinIO is used only in development.
Storefront (Next.js)Server-rendered storefront. The seller and delivery consoles are static Vite builds served by nginx.
Not running in production yet
  • imgproxy is not deployed: product images are served as the original uploads, not resized.
  • Email, SMS and push drivers are none. Notifications are created and queued but not sent; in-app notifications work. When SMTP (or an SMS or push account) is configured, the queued backlog goes out.
  • Card payments: Stripe and Paymob are unset. Cash on delivery only.
  • The admin console is not published.
Run one API instance

The go-live checklist records that the cache, realtime presence and a few other pieces are in-process today, so they are correct only for a single API instance until Redis-backed adapters exist. Scale vertically until then.

Health checks

  • GET /health — liveness; the process is up.
  • GET /ready — readiness; reports ready when the database answers, degraded when it does not.

Releases

  • The API image is built from infra/docker/api.Dockerfile (multi-stage, non-root) and tagged by commit, never latest.
  • Database migrations are forward-only (Prisma Migrate, applied with migrate deploy). Schema changes follow expand/contract so old and new code can run side by side — see docs/zero-downtime-migrations.md.
  • Rolling back after a release that migrated the database is not a simple image rollback; follow runbook §8.

Incident runbooks

docs/runbooks.md in the repository covers, among others:

  1. The ledger does not balance
  2. A payment webhook was dead-lettered
  3. Checkout is slow
  4. Database connections saturated
  5. The outbox is backing up
  6. Stock is oversold
  7. Driver cash does not match the ledger
  8. A deploy needs rolling back
  9. No drivers on shift
  10. Location pings are drowning the database
  11. The worker is down, or a job is not running

The launch readiness status of every item (blockers, data and recovery, observability, security) is tracked in docs/GO_LIVE_CHECKLIST.md.

Publishing this site

The site is plain HTML, one stylesheet and one small script, with no build step. Point an nginx server block for docs.helsen-med.com at the docs/site directory with index index.html; English pages sit at the root and Arabic pages under /ar/.