Dentolize · Regional DB Split Walkthrough
On this pageBusiness viewTechnical view

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 what ME-1 runs.
  • docker-compose.global.yml — an optional Docker-hosted shape for the Auth Server itself (auth-api, 2 replicas, plus redis). 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 by scripts/migrate-to-global.js and scripts/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/*.jsverify-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.