Deployment Overview
Simcha DB Studio ships as a single, self-contained, multi-arch container image (linux/amd64 + linux/arm64). You run it in your own environment; there is no Simcha-managed multi-tenant SaaS. The image is identical across Compose, Kubernetes, ECS, ACA, Cloud Run, and bare container runtimes, the orchestration manifest is the only thing that changes. The image is stateless; all durable state lives in one external PostgreSQL (the db_devops metadata store). That single fact drives every scaling, HA, and backup decision below.
Topology
+---------------------------+
| Reverse proxy / LB / IC |
| (nginx, ALB, Traefik...) |
+------+--------+-----------+
| |
/api/* / <- path-based split
:5002 :3000
| |
+------+--------+-----------+
| Simcha DB Studio |
| one container, tini PID 1 |
| node dist/server (API) :5002
| node server.js (Next) :3000
+------+--------------------+
|
+------+--------------------+
| PostgreSQL 13+ |
| metadata store: db_devops |
+---------------------------+
Process model
tini is PID 1; a supervising entrypoint (docker-entrypoint.sh) forks both Node processes and blocks on wait -n. The two long-lived processes are the Express API (:5002, auth, license, queries, ChangeOps, audit) and the Next.js standalone server (:3000, built with output: 'standalone'). This is a deliberate fail-fast supervisor: if either child exits, the entrypoint tears down the other and exits the container with that child's code, so the orchestrator restarts the whole task rather than leaving a half-dead pod (API up, UI dead). A SIGTERM/SIGINT trap forwards the signal to both children and drains before exit, so rolling deploys terminate cleanly. Port bindings are configurable via PORT (API, default 5002) and NEXT_PORT (UI, default 3000).
Reverse proxy contract
The two ports are a hard split, not a fallback: route /api/* to :5002 and everything else to :3000. Terminate TLS at the proxy/LB/ingress; the container speaks plaintext HTTP internally and does not manage certificates. Set CLIENT_URL to the exact external origin (scheme + host + port, no trailing slash), it is the CORS allowlist and a mismatch is the most common post-deploy failure.
Metadata database
One external PostgreSQL 13+ holds everything durable: users, connections, scripts, releases, executions, approvals, audit logs, license record, notifications, discovery inventory. Encrypted-at-rest columns (target-DB passwords, MFA secrets, repo credentials) are sealed with ENCRYPTION_KEY and are meaningless without it. The bundled single-node Postgres in the Compose/Helm artifacts is for evaluation only; production points at managed Postgres (RDS, Cloud SQL, Azure Database for PostgreSQL Flexible Server) or your own HA cluster.
Deployment paths
| Path | Fit | Artifact |
|---|---|---|
| Helm chart | Production on any K8s distribution (EKS, AKS, GKE, OpenShift, k3s, on-prem). The canonical HA path; migration Job and probes are pre-wired. | helm/ in the deployment package |
| Docker Compose | Single host, on-prem VM, evaluation. | docker-compose.yml + .env |
| ECS Fargate / Azure Container Apps / Cloud Run | Cloud-native no-K8s deploys; same image behind the vendor's LB. | Vendor manifest + the published image |
| Bare runtime | On-prem host without an orchestrator. | Any OCI runtime (Docker, Podman, containerd) |
Required secrets
The env validator refuses to boot in production if any of these is unset:
JWT_SECRET, HS256 signing key, ≥32 chars (64 hex recommended). Rotating it invalidates all live sessions.ENCRYPTION_KEY, AES-256-GCM master key for at-rest columns, ≥32 chars. Rotation is one-way without re-encryption tooling; back it up in versioned secret storage.DB_DEVOPS_DB_PASSWORD, metadata Postgres password (legacy aliasDB_PASSWORDaccepted; host/port/name/user follow the sameDB_DEVOPS_DB_*→DB_*fallback pair).
Licensing is RSA-signed and verified against the public key baked into the image, no license env var or outbound activation call is required at runtime. Apply your key via the UI (Settings → License) or POST /api/license/activate. Full reference: Environment Variables.
Startup gating and readiness
GET /api/health is always bound and returns 503 {status:"STARTING"} until the metadata DB is reachable, then 200 {status:"OK"}. The server does not hard-fail if Postgres is briefly unreachable at boot: an unref'd readiness poller retries initializeDatabase() on a 3s interval and completes initialization once the DB answers. Wire LB/ingress/orchestrator readiness to this endpoint so traffic is withheld until the store is live. GET /api/health/migrations returns the applied rows from schema_migrations (version, appliedAt) and is the deploy-time check for confirming a target DB is at the expected schema before cutting traffic over.
Migration runner
Migrations are idempotent, tracked in schema_migrations, and run by a standalone one-shot: node dist/server/database/migrate.js (exit 0 success / 1 failure, re-run on a current DB is a no-op). The image also exposes a dedicated migrate build stage whose default command is that runner, so you can build/pull a migration-only image or simply override the command on the full image. Two operating modes:
- On-boot (
MIGRATE_ON_BOOT=true, default). The entrypoint runs the migration before starting the app. It is intentionally non-fatal, on failure it logs a warning and still starts the app (so a first-run setup wizard can provision). Acceptable for single-instance Compose; unsafe as your only path under HA because concurrent replicas would race and a silent migration failure would not stop the rollout. - Explicit gate (recommended for HA). Run the one-shot as a pre-deploy step (K8s
pre-install/pre-upgradeJob or initContainer, ECSrun-task, ACI) that must exit 0 before the app rollout, and setMIGRATE_ON_BOOT=falseon the app replicas so migration has exactly one owner. The Helm chart wires this by default.
Distribution channels
- Docker Hub,
docker pull simchasolutions/db-studio:<version>. Mirror to ECR/ACR/GHCR for in-region pulls and supply-chain control. - Air-gapped, request the image as a tarball and
docker load -iit; no registry egress required. - Cloud marketplaces, AWS Marketplace (CloudFormation) and Azure Marketplace (ARM template + UI definition) publish the same image with a turnkey stack.
- Desktop, signed Electron builds (dmg/exe/AppImage/deb/rpm) for single-operator use, distinct from the server deployment covered here.
Always pin an immutable version tag (simchasolutions/db-studio:1.0.1) in production; reserve latest for evaluation.
Pick a path
- Any Kubernetes: Helm chart. K8s deployment.
- Single host / evaluation: Compose. Docker deployment.
- AWS, no K8s: ECS Fargate. ECS notes.
- Azure, no K8s: Container Apps. Azure notes.
- Scaling, HA, backup, hardening: Production & Scaling.