medOS ultra

Deployment Pipeline

How to go from code to running system across cloud, on-premise, and air-gapped environments.

5 min read diagramsUpdated 2026-04-15docs/DEPLOY_PIPELINE.md

How to go from code to running system — for cloud, on-premise, and air-gapped environments.


Overview

┌─────────────────────┐     ┌──────────────┐     ┌─────────────────────┐
│  Internet Machine    │     │   Transfer   │     │  Target Server      │
│  (your laptop or CI) │ ──► │   (USB/SCP)  │ ──► │  (VXRail/EC2/bare)  │
│                      │     │              │     │                     │
│  1. yarn install     │     │  .tar.gz     │     │  4. docker load     │
│  2. yarn build       │     │  (~3-6 GB)   │     │  5. docker compose  │
│  3. docker build     │     │              │     │  6. seed data       │
│  3b. docker save     │     │              │     │                     │
└─────────────────────┘     └──────────────┘     └─────────────────────┘

Phase 1: Build (Internet-Connected Machine)

1a. Install Dependencies

cd medOS-ultra

# Fix known dep issue
sed -i 's/"moleculer-db-adapter-mongoose": "\^0.9.12"/"moleculer-db-adapter-mongoose": "^0.10.1"/' services/interoperability/package.json

# Install
yarn install --network-timeout 600000

1b. Build Shared Packages + API Services

# Build shared platform packages first (required by services)
yarn build:platform

# Build all microservices
yarn build:api

# Verify — each service should have a dist/ folder
ls services/gateway/dist/
ls services/auth/dist/
ls services/clinical/dist/
# etc.

1c. Build Docker Images

# Build the single API image (used by all 13 NestJS services)
docker build -t medos-api:latest \
  -f docker/api/Dockerfile \
  --network=host \
  --build-arg SCOPE=ever-api-gateway .

# Build supporting images
docker build -t medos-eform:latest -f docker/eform/Dockerfile .   # if eform paths are set up
docker pull mongo:5
docker pull postgres:14.5
docker pull nats:2-alpine
docker pull nginx:1.22
docker pull ipfs/go-ipfs:master

1d. Save Images for Transfer

docker save \
  medos-api:latest \
  mongo:5 postgres:14.5 nats:2-alpine nginx:1.22 \
  "ipfs/go-ipfs:master" \
  | gzip > medos-images.tar.gz

ls -lh medos-images.tar.gz  # ~3-5 GB

1e. Or Use the Packaging Script

cd infrastructure
./scripts/package-airgap.sh thailand
# Output: medos-airgap-thailand-YYYYMMDD.tar.gz

Phase 2: Transfer

Method Speed Use When
USB 3.0 drive ~100 MB/s, ~60s Air-gapped hospital
SCP/rsync over LAN ~100 MB/s Same network
S3 upload → download Varies Cloud staging
# USB
cp medos-airgap-thailand-*.tar.gz /Volumes/USB_DRIVE/

# SCP
scp medos-airgap-thailand-*.tar.gz user@hospital-server:/opt/medos/

# rsync (resumable)
rsync -avzP medos-airgap-thailand-*.tar.gz user@server:/opt/medos/

Phase 3: Deploy on Target Server

3a. Prerequisites on Server

# Ubuntu 22.04 with Docker installed
docker --version   # 24.x+
docker compose version  # v2.x

3b. Extract and Load

cd /opt/medos
tar xzf medos-airgap-thailand-*.tar.gz
cd medos-airgap-thailand-*

# Load Docker images (no internet needed)
gunzip -c medos-images.tar.gz | docker load
# Takes 5-10 minutes

3c. Configure

# Generate SSL cert
bash scripts/generate-ssl-cert.sh medos.local

# Create env file
cp .env.onpremise .env.onpremise.thailand
nano .env.onpremise.thailand
# Set: BASE_URL, TZ=Asia/Bangkok, JWT_SECRET, SOCKET_IO_TOKEN

# Add to /etc/hosts
echo "127.0.0.1 medos.local" | sudo tee -a /etc/hosts

3d. Start Everything

docker compose \
  --env-file .env.onpremise.thailand \
  -f docker-compose-onpremise.yml \
  up -d

# Watch startup
docker compose -f docker-compose-onpremise.yml logs -f
# Wait for "Gateway started" (~60-90 seconds)

3e. Load Seed Data

# Thailand (default seeds in web/scripts/ and web/supabase/migrations/)
# These need Supabase configured — run against your Supabase instance

# Japan seeds (additional)
for sql in market-packs/medos-japan/seed-*.sql; do
  echo "Loading: $sql"
  psql "$SUPABASE_DB_URL" -f "$sql"
done

# Philippines seeds (additional)
for sql in market-packs/medos-philippines/seed-*.sql; do
  echo "Loading: $sql"
  psql "$SUPABASE_DB_URL" -f "$sql"
done

# English seeds (additional)
for sql in market-packs/medos-english/seed-*.sql; do
  echo "Loading: $sql"
  psql "$SUPABASE_DB_URL" -f "$sql"
done

# Blood donor seeds (TypeScript — need Node on server)
cd /path/to/web
cp .env.onpremise.thailand .env
npx tsx ../infrastructure/market-packs/medos-japan/seed-blood-donors.ts
npx tsx ../infrastructure/market-packs/medos-philippines/seed-blood-donors.ts
npx tsx ../infrastructure/market-packs/medos-english/seed-blood-donors.ts

3f. Verify

# All containers running
docker compose -f docker-compose-onpremise.yml ps

# API health
curl -k https://medos.local/api/health

# Open browser
open https://medos.local

Deployment Modes Summary

Mode Build Where Run Where Internet Needed
Cloud (AWS) GitHub Actions / local ECS Fargate Yes (always)
EC2 Demo On the EC2 itself Docker Compose on EC2 Yes (for build)
On-Premise (connected) On the server Docker Compose Yes (for initial build)
Air-Gapped Internet machine → USB Docker Compose No (uses pre-built images)

Seed Data by Region

All seeds are additive and idempotent. You can load multiple regions into the same database.

Market Pack IDs Prefix Locale Facilities
Thailand (default) (none) th Hospital + Clinic
Japan jp-* ja Hospital + 特別養護老人ホーム
Philippines ph-* fil Hospital + Nursing Home
English en-* en Hospital + Skilled Nursing

Updating an Existing Installation

# On internet machine: rebuild
yarn install && yarn build:platform && yarn build:api
docker build -t medos-api:latest -f docker/api/Dockerfile --network=host .
docker save medos-api:latest | gzip > medos-api-update.tar.gz

# Transfer to server
scp medos-api-update.tar.gz user@server:/opt/medos/

# On server: load new image and restart
gunzip -c medos-api-update.tar.gz | docker load
docker compose -f docker-compose-onpremise.yml up -d
# Data volumes are preserved — no data loss

CI/CD (GitHub Actions)

For automated deployment to the demo EC2:

# .github/workflows/deploy.yml
name: Deploy to Demo
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: yarn install && yarn build:platform && yarn build:api
      - run: |
          ssh user@${{ secrets.DEMO_SERVER_IP }} "
            cd /opt/medos/medOS-ultra
            git pull
            yarn install && yarn build:platform && yarn build:api
            docker compose -f infrastructure/docker-compose-onpremise.yml restart
          "

Quick Reference

# Build (internet machine)
yarn install && yarn build:platform && yarn build:api

# Package for air-gap
cd infrastructure && ./scripts/package-airgap.sh thailand

# Deploy (target server)
gunzip -c medos-images.tar.gz | docker load
docker compose --env-file .env.onpremise.thailand -f docker-compose-onpremise.yml up -d

# Load all seeds
./deploy.sh thailand seed
./deploy.sh japan seed
./deploy.sh philippines seed

# Start/Stop/Restart
docker compose -f docker-compose-onpremise.yml up -d
docker compose -f docker-compose-onpremise.yml down
docker compose -f docker-compose-onpremise.yml restart

# Backup
./backup.sh

# Update (no data loss)
docker load < new-images.tar
docker compose -f docker-compose-onpremise.yml up -d
Ask Anything