12-operations-runbookswave: W6filled0 citations

Go Live from Git Clone — End-to-End Deployment Runbook

Audiences: internal, developer, partner

Go Live from Git Clone — End-to-End Deployment Runbook

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

Rendering diagram…

Tech Stack & Choices

ConcernChoiceRationale
Bring-up toolddx-install unattendedconverges with the attended wizard on one ResolvedTenantConfig; zero-prompt + resumable
BackboneDocker-first (ddx-docker-full-hms)proven 19-service compose; migrations owned by the db-init service
Validation harnessddx-api-usageREST scenario matrix against the live API; scenario 03 is a self-contained create+fetch
SecretsENV:VARNAME indirectiononly external keys come from env; internal hex32 secrets are minted

Data Flow

Prerequisites

RequirementMinimumNotes
Docker≥ 24 (Compose v2)infra + app images run here
Node20+matches repo toolchain
pnpm9.15.4 via corepackpnpm 10 breaks image builds (esbuild/sharp/prisma/@swc)
CLI toolsjq, openssl, curlsecret-gen, manifest, health curls
Disk / RAM≥ 50 GB / ≥ 8 GB19-service stack + volumes; voice profiles add headroom

The runbook (ordered)

  1. Install prerequisites — verify Docker ≥24, Node 20+, and pin pnpm: corepack prepare pnpm@9.15.4 --activate. Confirm jq + openssl.
  2. Clone the umbrella into a sibling dir: git clone <umbrella> ~/projects/sandboxes/<tenant>.
  3. pnpm install inside ddx-install/ (tsx + the CLI deps).
  4. 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.
  5. ddx-install doctor — host preflight (docker, disk, RAM, tool versions, port availability of the chosen block, network reachability). Aborts on FAIL.
  6. Author tenant-unattended.json — trigram, port block (a free 1000-port block; the run used 46xxx), mode: onprem, capabilities (voice/calendar/ocr), external dep URLs via ENV: indirection. LiveKit UDP range must be exactly 101 ports wide (e.g. 60000-60100).
  7. 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.
  8. Verify:
    • curl http://localhost:<api>/api/v1/health{status:ok, database:up, fhir:up}
    • curl http://localhost:<fhir>/fhir/metadata → CapabilityStatement
    • POST /auth/login (seeded admin) → HTTP 200 + JWT
    • ddx-api-usage scenario 03-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; unattended command + 11-step runExecute()
  • ddx-docker-full-hms/scripts/provision-tenant.sh — canonical provisioner wrapped by the installer
  • ddx-docker-full-hms/docker-compose.yml — 19-service backbone; db-init owns migrations
  • ddx-seeder-ts/data/ — PLATFORM-DATA + 14 phase dirs; must be staged into the tenant dir
  • plans/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.

SymptomCauseFix
validate-manifest.sh can't find schemascript hard-references tenant-data/_template/manifest.schema.json; the canonical 4059-byte schema lives at the umbrella root, not under ddx-docker-full-hmsstage 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 failurestale postgres volume retained an old/null passworddocker compose -p ddx-hms-<trigram> down -v to re-init postgres with the freshly minted password
migration cutover gate blocks fresh installSaaS-removal migration's _saas_cutover_ready marker is absent on a clean DBcreate the sanctioned marker (no data at risk on a fresh DB); installer follow-up: auto-create on fresh install
migration aborts with P3009a prior failed gate raise left a failed _prisma_migrations rowdelete the failed-attempt row → migrate re-runs cleanly
host-side prisma not found at step 7Docker-mode migrations are owned by the compose db-init service, not the hoststep 7 is a Docker-mode no-op; do not run migrate-deploy.sh from the host
/health times out vs a healthy APIhealth 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.jsoninstaller never staged the seed-data tree into the tenant dirstage the full ddx-seeder-ts/data/ (PLATFORM-DATA + 14 phase dirs) into tenant-data/<trigram>/
/patients /practitioners return 0 after seedmulti-tenancy interceptor scopes reads to the caller's org; patient phase didn't populate that orgnot 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.

    Go Live from Git Clone — End-to-End Deployment Runbook — Dudoxx Docs | Dudoxx