Go Live from Git Clone — End-to-End Deployment Runbook
Audiences: internal, developer, partner
The ordered runbook for bringing up a brand-new DDX HMS server from a fresh git clone to a live, seeded, FHIR-backed stack. Every step here was executed on a real fresh-clone provisioning run (trigram
fix, ports 46xxx,255e9e95d) — the troubleshooting table below distills the 8 real findings that run surfaced and fixed.
Business Purpose
A new on-prem or partner deployment must be reproducible and verifiable, not a
folklore of hand-edited env files. This runbook is the canonical path: clone →
validate wiring → preflight → author the unattended config → provision →
verify. Following it, a fresh host reaches a working stack (auth + persistence +
FHIR) in one ddx-install unattended invocation, with a parity gate that
catches silent-mismatch failures before any service starts.
Audiences
- Internal (deployment ops): copy-paste runbook to stand up a tenant.
- Developer: understand what each rung proves and where fresh installs trip.
- Partner: deploy DDX HMS into customer infrastructure with a validated path.
Architecture
Tech Stack & Choices
| Concern | Choice | Rationale |
|---|---|---|
| Bring-up tool | ddx-install unattended | converges with the attended wizard on one ResolvedTenantConfig; zero-prompt + resumable |
| Backbone | Docker-first (ddx-docker-full-hms) | proven 19-service compose; migrations owned by the db-init service |
| Validation harness | ddx-api-usage | REST scenario matrix against the live API; scenario 03 is a self-contained create+fetch |
| Secrets | ENV:VARNAME indirection | only external keys come from env; internal hex32 secrets are minted |
Data Flow
Prerequisites
| Requirement | Minimum | Notes |
|---|---|---|
| Docker | ≥ 24 (Compose v2) | infra + app images run here |
| Node | 20+ | matches repo toolchain |
| pnpm | 9.15.4 via corepack | pnpm 10 breaks image builds (esbuild/sharp/prisma/@swc) |
| CLI tools | jq, openssl, curl | secret-gen, manifest, health curls |
| Disk / RAM | ≥ 50 GB / ≥ 8 GB | 19-service stack + volumes; voice profiles add headroom |
The runbook (ordered)
- Install prerequisites — verify Docker ≥24, Node 20+, and pin pnpm:
corepack prepare pnpm@9.15.4 --activate. Confirmjq+openssl. - Clone the umbrella into a sibling dir:
git clone <umbrella> ~/projects/sandboxes/<tenant>. pnpm installinsideddx-install/(tsx + the CLI deps).ddx-install graph— validates the wiring graph with no side effects. Healthy output: 36 logical keys, 17 critical edges, 14 consumer modules; every critical edge has ≥2 consumers.ddx-install doctor— host preflight (docker, disk, RAM, tool versions, port availability of the chosen block, network reachability). Aborts on FAIL.- Author
tenant-unattended.json— trigram, port block (a free 1000-port block; the run used46xxx),mode: onprem, capabilities (voice/calendar/ocr), external dep URLs viaENV:indirection. LiveKit UDP range must be exactly 101 ports wide (e.g.60000-60100). ddx-install unattended tenant-unattended.json— runs the 11-step pipeline: validate-manifest (pre) → generate-secrets → wire per-module.env→ validate-manifest (post) → manifest-to-env →compose up --build→ wait postgres → db-init migrate → wait ddx-api → seed → optional profiles → print portal URL.- Verify:
curl http://localhost:<api>/api/v1/health→{status:ok, database:up, fhir:up}curl http://localhost:<fhir>/fhir/metadata→ CapabilityStatementPOST /auth/login(seeded admin) → HTTP 200 + JWTddx-api-usagescenario03-patient-create-and-fetch→ POST 201 + GET 200, fetched id/name match POSTed payload (proves auth → gateway → RBAC → tenant-scoping → Prisma + HAPI FHIR → read-back).
Implicated Code
ddx-install/— the CLI;unattendedcommand + 11-steprunExecute()ddx-docker-full-hms/scripts/provision-tenant.sh— canonical provisioner wrapped by the installerddx-docker-full-hms/docker-compose.yml— 19-service backbone;db-initowns migrationsddx-seeder-ts/data/— PLATFORM-DATA + 14 phase dirs; must be staged into the tenant dirplans/ddx-install/progress/T5.2.md— the fresh-clone validation run this runbook is grounded in
Operational Notes
Troubleshooting table — these are the real gotchas a fresh install hits,
distilled from the T5.2 fresh-clone run. Each was caught only by an actual
provisioning, not by static review or --dry-run.
| Symptom | Cause | Fix |
|---|---|---|
validate-manifest.sh can't find schema | script hard-references tenant-data/_template/manifest.schema.json; the canonical 4059-byte schema lives at the umbrella root, not under ddx-docker-full-hms | stage the canonical schema into ddx-docker-full-hms/tenant-data/_template/ (substrate follow-up: resolve at root) |
validate-manifest rejects the manifest (additionalProperties:false) | toManifest() emitted stray top-level keys + omitted required internal.{vivoxx,jitsi,livekit} and seeding.{sharedPath,commonsPath,phases} | the installer now emits a schema-valid manifest; if hand-editing, match the canonical schema exactly |
| minio FATAL "MINIO_ROOT_PASSWORD length ≥ 8" | secrets.json missing a key manifest-to-env.sh reads → emits literal "null" | seed all 15 keys of the secrets contract (minioRootPassword, pgPassword, …) |
| db-init P1000 auth failure | stale postgres volume retained an old/null password | docker compose -p ddx-hms-<trigram> down -v to re-init postgres with the freshly minted password |
| migration cutover gate blocks fresh install | SaaS-removal migration's _saas_cutover_ready marker is absent on a clean DB | create the sanctioned marker (no data at risk on a fresh DB); installer follow-up: auto-create on fresh install |
| migration aborts with P3009 | a prior failed gate raise left a failed _prisma_migrations row | delete the failed-attempt row → migrate re-runs cleanly |
host-side prisma not found at step 7 | Docker-mode migrations are owned by the compose db-init service, not the host | step 7 is a Docker-mode no-op; do not run migrate-deploy.sh from the host |
/health times out vs a healthy API | health check read top-level body.status; ddx-api wraps all responses in {data,meta} | unwrap the envelope: body.data?.status ?? body.status |
seeder ENOENT roles.json | installer never staged the seed-data tree into the tenant dir | stage the full ddx-seeder-ts/data/ (PLATFORM-DATA + 14 phase dirs) into tenant-data/<trigram>/ |
/patients /practitioners return 0 after seed | multi-tenancy interceptor scopes reads to the caller's org; patient phase didn't populate that org | not a stack failure — a seed/tenant-scoping gap; seed the caller's tenant or use seed:all |
Verdict from the live run: the installer provisioned a working full-stack DDX HMS from a fresh clone — 8 containers up/healthy, 179 migrations across 5 schemas, 1032+ seeded clinical records, working auth + FHIR. Tear down with
cd ddx-docker-full-hms && docker compose -p ddx-hms-<trigram> down -v.
Related Topics
- DDX Install CLI — the installer this runbook drives
- Full HMS Compose Infrastructure — the 19-service backbone
- Tenant Provisioner — in-stack clinic onboarding (vs whole-stack bring-up)
- Environment Wiring — the typed wiring engine + 17 critical edges
- Seeder Phases — the 11-phase platform seed sequence