Deployment Infrastructure
Infrastructure layer: Docker, Terraform, Supabase migrations, market packs.
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 + nonprodhis/deployment-stack/his-vajira-demo.git— demo environmenthis/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:
terminology_cache(Postgres) — <10ms- Local JSON ValueSets (packaged with edge functions)
- Snowstorm upstream (SNOMED CT only)
- 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. |