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