medOS ultra

Deployment Infrastructure

Infrastructure layer: Docker, Terraform, Supabase migrations, market packs.

7 min read diagramsUpdated 2026-03-26docs/architecture/architecture/10-deployment-infrastructure.md

GKE + on-premise Kubernetes. GitLab CI/CD. Multi-stage Docker builds. Market packs for multi-tenant. Cloud and on-prem from the same artifacts.


Overview

MedOS deploys as containerized microservices on both Google Kubernetes Engine (GKE) and on-premise Kubernetes clusters, from the same Docker artifacts. Environment-specific configuration lives in separate deployment stack repositories.


Deployment Architecture

┌──────────────────────────────────────────────────────────────────────────────┐
│                        SOURCE CODE (GitLab)                                   │
│                                                                              │
│  his-vajira/ (Turbo monorepo)                                                │
│  ├── apps/ever/microservices/* (14 services)                                 │
│  ├── apps/ever/ui/web/ (Next.js frontend)                                    │
│  ├── docker/ (Dockerfiles + compose files)                                   │
│  └── .gitlab-ci.yml (CI/CD pipeline)                                         │
└──────────────────────────────────┬───────────────────────────────────────────┘
                                   │
                    Branch Push Triggers CI/CD
                                   │
                                   ▼
┌──────────────────────────────────────────────────────────────────────────────┐
│                        GITLAB CI/CD PIPELINE                                  │
│                                                                              │
│  BUILD STAGE                                                                 │
│  ┌──────────────────────────────────────────────────────────────────────┐    │
│  │  Multi-stage Docker builds (node:22-alpine + Turbo + Bun)           │    │
│  │                                                                      │    │
│  │  Images built:                                                       │    │
│  │  his-ever-backend     (14 microservices in 1 image, scope-selected)  │    │
│  │  his-ever-frontend    (Next.js SSR)                                  │    │
│  │  his-ever-eform       (Toolpad form builder)                         │    │
│  │  his-ever-reverse-proxy (Nginx)                                      │    │
│  │                                                                      │    │
│  │  Registry: asia-southeast1-docker.pkg.dev/evermed-devops-prod/his/   │    │
│  └──────────────────────────────────────────────────────────────────────┘    │
│                                                                              │
│  DEPLOY STAGE (5 parallel paths)                                             │
│  ┌────────────────┐ ┌────────────────┐ ┌────────────────┐                   │
│  │ cloud-nonprod   │ │ cloud-prod     │ │ cloud-demo     │                   │
│  │ Branch: develop │ │ Branch: main   │ │ Branch: demo   │                   │
│  │ → GKE dev       │ │ → GKE prod     │ │ → GKE demo     │                   │
│  └────────────────┘ └────────────────┘ └────────────────┘                   │
│  ┌────────────────┐ ┌────────────────┐                                      │
│  │ onprem-nonprod  │ │ onprem-prod    │                                      │
│  │ Branch: develop │ │ Branch: main   │                                      │
│  │ → Customer K8s  │ │ → Customer K8s │                                      │
│  └────────────────┘ └────────────────┘                                      │
│                                                                              │
│  Branch Mapping: develop→development, test→alpha, main→production            │
└──────────────────────────────────────────────────────────────────────────────┘
                                   │
                    Helm deploy to target cluster
                                   │
                                   ▼
┌──────────────────────────────────────────────────────────────────────────────┐
│                    RUNTIME INFRASTRUCTURE                                     │
│                                                                              │
│  ┌────────────────────────────────────────────────────────────────────────┐  │
│  │  KUBERNETES CLUSTER (GKE or On-Premise)                                │  │
│  │                                                                        │  │
│  │  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐    │  │
│  │  │ api-gateway  │ │ api-aaa     │ │ api-clinical │ │ api-financial│    │  │
│  │  │ :3001        │ │ Auth/AuthZ  │ │ Clinical     │ │ Billing      │    │  │
│  │  ├─────────────┤ ├─────────────┤ ├─────────────┤ ├─────────────┤    │  │
│  │  │ api-diag     │ │ api-meds    │ │ api-admin    │ │ api-found    │    │  │
│  │  ├─────────────┤ ├─────────────┤ ├─────────────┤ ├─────────────┤    │  │
│  │  │ api-filestore│ │ api-eform   │ │ api-interop  │ │ api-migrate  │    │  │
│  │  ├─────────────┤ ├─────────────┤ ├─────────────┤ ├─────────────┤    │  │
│  │  │ api-messaging│ │ api-printing │ │ public-api   │ │ global-seq   │    │  │
│  │  └─────────────┘ └─────────────┘ └─────────────┘ └──────────────┘   │  │
│  │                                                                        │  │
│  │  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐    │  │
│  │  │ Frontend     │ │ eForm       │ │ Reverse Proxy│ │ Reporting    │    │  │
│  │  │ (Next.js)    │ │ (Toolpad)   │ │ (Nginx)      │ │ (Spring)     │    │  │
│  │  │ :3000        │ │ :8085       │ │ :80/:443     │ │ :8088        │    │  │
│  │  └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘    │  │
│  └────────────────────────────────────────────────────────────────────────┘  │
│                                                                              │
│  ┌────────────────────────────────────────────────────────────────────────┐  │
│  │  STATEFUL SERVICES                                                     │  │
│  │                                                                        │  │
│  │  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐   │  │
│  │  │ MongoDB   │ │ NATS     │ │ IPFS +   │ │ Postgres │ │ FTP      │   │  │
│  │  │ (Replica) │ │ Message  │ │ Cluster  │ │ (eForm)  │ │ (Legacy) │   │  │
│  │  │ :27017    │ │ Bus      │ │ File     │ │ :5432    │ │ :21      │   │  │
│  │  │           │ │ :4222    │ │ Storage  │ │          │ │          │   │  │
│  │  └──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘   │  │
│  └────────────────────────────────────────────────────────────────────────┘  │
│                                                                              │
│  ┌────────────────────────────────────────────────────────────────────────┐  │
│  │  EXTERNAL SERVICES                                                     │  │
│  │  Supabase (MedBase)  │  Dify (AI Workflows)  │  Metriport (HIN)       │  │
│  └────────────────────────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────────────────────┘

Docker Build Strategy

All images use multi-stage builds with Turbo monorepo pruning + Bun for fast installs.

Image Base Dockerfile Heap Port(s)
Backend APIs node:22-alpine docker/api/Dockerfile 4GB Per-service
Frontend node:22-alpine docker/web/Dockerfile 20GB (build) 3000
eForm node:18.13 docker/eform/Dockerfile 4GB 8085
Reverse Proxy nginx:1.22 docker/reverse-proxy/Dockerfile 80, 443
Reporting Spring Boot ever-api-printing/ 8088

Build stages: base → pruner (Turbo scope) → installer (Bun) → builder (TypeScript) → runner (minimal)


Environment Tiers

Environment Branch Namespace Trigger
development develop development Auto on push
alpha test alpha Auto on push
demo demo development Auto on push
production main production Manual approval

Deployment stack repos (external):

  • his/deployment-stack/his-vajira.git — cloud prod + nonprod
  • his/deployment-stack/his-vajira-demo.git — demo environment
  • his/deployment-stack/his-vajira-onprem.git — on-premise deployments

Market Packs (Multi-Tenant Configuration)

Market packs enable hospital-specific data migration and compliance profiles:

his-vajira/market-packs/
├── _template/
│   └── manifest.json          # Template for new market packs
├── bizbox-thailand/
│   ├── manifest.json          # Source HIS: Bizbox, Country: Thailand
│   ├── code-mappings/
│   │   ├── gender.json        # Source gender codes → medOS
│   │   └── pttype-insurance.json  # Patient type → insurance scheme
│   └── schema-hints/
│       └── core-tables.json   # Source DB schema hints

Manifest structure:

{
  "code": "bizbox-thailand",
  "sourceHis": "Bizbox",
  "country": "Thailand",
  "complianceProfile": {
    "dataResidency": "local_only",
    "retentionDays": 90,
    "phiMaskingLevel": "strict"
  },
  "knownQuirks": [
    "Thai2unicode encoding in older installations",
    "vstdate uses Buddhist Era — add 543 to convert to CE"
  ]
}

Resource Configuration

Service Pool/Limits Notes
MongoDB minPool=100, maxPool=500 64,000 file descriptors
Nginx 4 workers, 512 connections each 100MB body, 600s timeout
NATS 64MB max payload 40s write deadline
Node.js APIs 4GB heap Per-service container
Node.js Frontend 20GB heap (build only) Runtime is lighter

SNOMED CT Terminology Server (Self-Hosted)

Deployed via separate Docker Compose for clinical coding:

docker/snowstorm/docker-compose.snowstorm.yml

┌────────────────────┐     ┌──────────────────────────┐
│ Elasticsearch 7.17 │────▶│ Snowstorm 10.2.0         │
│ 2GB JVM heap       │     │ SNOMED CT Browser + FHIR │
│ Single-node cluster│     │ Port: 8085               │
│                    │     │ 1-2GB JVM heap            │
└────────────────────┘     └──────────────────────────┘

Resolution chain for terminology lookups:

  1. terminology_cache (Postgres) — <10ms
  2. Local JSON ValueSets (packaged with edge functions)
  3. Snowstorm upstream (SNOMED CT only)
  4. Not found

Cloud vs On-Premise

Aspect Cloud (GKE) On-Premise
Registry Google Artifact Registry Customer-managed
Orchestration GKE with Helm Customer Kubernetes + Helm
Secrets GCP Secret Manager Customer vault
Networking GCP Load Balancer Customer ingress
Storage GCS + managed MongoDB Atlas Local PV + self-hosted MongoDB
Deploy repo his-vajira.git his-vajira-onprem.git
Same artifacts Yes Yes — same Docker images

Key design: Artifacts are environment-agnostic. All configuration is injected via Helm values and ConfigMaps at deploy time.


Why This Matters

For the CTO Benefit
Dual deployment Same images deploy to GKE and on-prem K8s. No code forks.
Market expansion Market packs isolate country/hospital-specific configs. Add a new customer without touching core.
CI/CD maturity Automated build + deploy on branch push. Manual gate for production.
Cost control Multi-stage Docker builds minimize image sizes. Turbo pruning only builds affected services.
Data sovereignty dataResidency: "local_only" in market packs. On-prem option for regulated markets.
Terminology independence Self-hosted Snowstorm eliminates external SNOMED CT API dependency.
Ask Anything