Deployment & Infrastructure
Business view
Running two databases and two kinds of server (a global Auth Server, and one regional server per region) means the way Dentolize gets deployed has to change too — and this PR deliberately doesn't force a single answer. It lets each region be deployed the way that fits it best. The EU region, which already runs on PM2 processes on an existing host, keeps doing that. A new region can instead run as Docker containers on any cloud host, with no PM2 or pre-provisioned server required. Both approaches are wired into the same codebase, the same Docker image, and the same set of deployment scripts, and both are covered by a very long, very literal go-live runbook so the actual cutover of production traffic is a checklist, not tribal knowledge.
Technical view
Containerizing the services
Dockerfile (211 lines, multi-stage) produces three deployable images from one file: server (the regional API/cron/queue), auth-server (the global Auth Server), and whatsapp-official (regional WhatsApp integration) — plus a prisma tooling stage used by local/dry-run environments. Each final image copies only its own split Prisma client and migrations (migrations-global or migrations-regional), listens on its own port (server → 4000, auth-server → 4001, whatsapp-official → 4001 within its own container), and has a healthcheck endpoint (/.well-known/apollo/server-health for the API, /health for the others). .dockerignore keeps the images server-only — no clinic-web/patient-web source, no .env files, no docs.
Compose topologies
Four docker-compose.*.yml files serve different purposes:
docker-compose.regional.yml— the production shape for a Docker-hosted region:api,cron,queue,whatsapp,redis, plus a local MinIO for S3-compatible storage. This is whatME-1runs.docker-compose.global.yml— an optional Docker-hosted shape for the Auth Server itself (auth-api, 2 replicas, plusredis). Production currently runs the Auth Server under PM2 on the EU-1 host instead — this compose file exists for non-production or alternative deployments.docker-compose.local.yml(621 lines) — a full two-region local/smoke-test stack: global + two regional Postgres databases, three Redis instances, storage emulation (MinIO ×2, fake GCS), both regional APIs, queues, and WhatsApp services, plus the smoke-test runner itself.docker-compose.dryrun.yml— a smaller rehearsal stack that simulates the migration path itself (monolith database being treated as a regional database, alongside a fresh global database), driven byscripts/migrate-to-global.jsandscripts/dry-run/run.sh.
Rollout scripts
rollout-regional-docker.sh and rollout-global-docker.sh wrap docker compose with environment-file resolution, image-tag pinning, and a Prisma-migration mode flag (--prisma=monolith|regional|skip). rollout-global-server-pm2.sh and rollout-regional-server-pm2.sh do the equivalent for the PM2 deployment path (the latter does a rolling reload across API/WhatsApp processes with a configurable delay). rollout-frontend.sh handles the (non-containerized) clinic-web frontend build/deploy. DEPLOYMENT.md at the repo root documents the actual production topology these scripts target: EU-1 on PM2 (API :4000, Auth :4001, WhatsApp :4002), ME-1 on Docker (API :4000, WhatsApp :4001).
Cutover runbook and verification tooling
scripts/GO_LIVE_RUNBOOK.md (1,108 lines) is a phase-by-phase production cutover checklist: Phase A (prepare before the maintenance window — freeze release, back up, provision new databases, prepare ME-1, prepare storage, install a maintenance gate, dry-run), Phase B (the maintenance window itself — stop producers, final backup, extract global data from the existing monolith, convert EU-1 to the new regional shape, start new runtimes, run verification checks V1–V8, functional checks, sign off), Phase C (post-reopen reconciliation of payments, WhatsApp, and mappings), plus an explicit pre-open rollback procedure.
The V1–V8 checks referenced in Phase B are scripts/verify/*.js — verify-schema-generation.sh, verify-global-db-structure.js (confirms expected tables exist in the global DB and unexpected ones don't), verify-data-extraction.js, verify-region-config.js (validates REGIONAL_API_URLS_JSON and flags any local/loopback URL — a production sanity check), verify-mappings.js, verify-regional-db.js, verify-cross-db-access.js, verify-auth-routing.js, and an aggregator verify-full-integrity.js.
Smoke tests
scripts/smoke-test/ (@dentolize/smoke-tests) is a large Vitest suite — 36 test files — that stands up the full local two-region Docker stack (docker-compose.local.yml + smoke-specific overrides), seeds global and both regional databases, and runs end-to-end checks against real HTTP/GraphQL/DB/Redis/storage behavior: health, email/company lookup, user/company mapping, GraphQL auth, rate limiting, Redis isolation between regions, registration and region selection, admin regional parity, storage regionalization, WhatsApp regionalization, global uniqueness, public link/QR routing, payment callback URLs, upload/import handoff, password reset, cron controls, and — directly exercising the tool described in Moving a Company Between Regions — a full company-region-migration test. scripts/smoke-test/run.sh orchestrates the whole thing and tears the environment down afterward; it runs against an ephemeral local stack, never against production or staging.