6.3 KiB
Deployment
Prerequisites
- Docker + Docker Compose
- Reverse proxy (Caddy, Nginx, Traefik) for TLS termination
- At least one configured AI provider (Bedrock / Azure / Vertex / LiteLLM / OpenRouter)
Images
| Image | Role |
|---|---|
danielonyejesi/pediatric-ai-scribe-v3:latest |
App container. Published by CI on every tag push where configured. Pull directly or build from source. |
pgvector/pgvector:pg16 |
Database. |
redis:7-alpine |
Operational Redis cache/state. |
Build from source
git clone https://github.com/ifedan-ed/pediatric-ai-scribe-v3.git
cd pediatric-ai-scribe-v3
cp .env.example .env
# edit .env — required: APP_URL, JWT_SECRET, DATA_ENCRYPTION_KEY, DB_PASSWORD, an AI provider
docker compose up -d --build
The default compose starts pediatric-ai-scribe on 127.0.0.1:3552, pedscribe-db internally, and ped-ai-redis internally.
Minimum .env
APP_URL=https://scribe.example.com
JWT_SECRET=<openssl rand -hex 32>
DATA_ENCRYPTION_KEY=<openssl rand -hex 32>
DB_PASSWORD=<strong password>
AI_PROVIDER=litellm
LITELLM_API_BASE=https://llm.example.com
LITELLM_API_KEY=sk-...
Full variable reference: docs/configuration.md.
Reverse proxy
App binds to 127.0.0.1:3552 only. TLS termination + host routing is the
proxy's job.
Caddy
scribe.example.com {
reverse_proxy localhost:3552
}
Nginx
server {
listen 443 ssl http2;
server_name scribe.example.com;
ssl_certificate /etc/ssl/certs/scribe.example.com.pem;
ssl_certificate_key /etc/ssl/private/scribe.example.com.key;
client_max_body_size 100M;
location / {
proxy_pass http://127.0.0.1:3552;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
App sets trust proxy: 1 so rate limiting uses the original client IP.
Volumes
| Volume | Contents | Backup priority |
|---|---|---|
pgdata |
All user data, encounters, memories, audit logs, settings, embeddings | Critical |
scribe-logs |
Filesystem audit log files (JSONL by day) | High for compliance evidence; Postgres also has audit/API/access tables |
Postgres backup / restore
# Backup
docker exec pedscribe-db pg_dump -U pedscribe pedscribe > backup.sql
# Restore
cat backup.sql | docker exec -i pedscribe-db psql -U pedscribe pedscribe
Updating
From a Docker Hub pull
docker compose pull
docker compose up -d
Building from source
git pull
docker compose build --no-cache
docker compose up -d
On startup the container runs initDatabase() (idempotent baseline), then
node-pg-migrate applies any new migration files. Collation-drift check auto-
REINDEXes if the ICU library version changed between image builds.
Health
| Endpoint | Purpose |
|---|---|
GET /api/health |
{ok:true} — public, used by Docker health check |
GET /api/health/detailed |
Provider status — admin-auth required |
GET /api/build |
Build ID (short git SHA) — useful for debugging cache invalidation |
GET /metrics |
Prometheus metrics in text exposition format |
Docker health check in Dockerfile: every 30 s, wget-spiders /api/health.
Container marked unhealthy after 5 failures.
Resource footprint
- RAM: 256 MB minimum, 512 MB recommended for one instance with a handful of concurrent users.
- Disk: Postgres size scales with audit log retention, saved encounters, documents, and Learning Hub content.
- CPU: idle load negligible; AI calls are network-bound on the LLM provider side.
Production checklist
JWT_SECRET≥ 32 bytes (openssl rand -hex 32)DATA_ENCRYPTION_KEYexactly 64 hex charsDB_PASSWORDnon-defaultAPP_URL= public URL (enables fail-closed CORS + HSTS + secure cookies)- HIPAA workload → use Bedrock, Azure OpenAI, or Vertex (all BAA-eligible). Not OpenRouter or ElevenLabs.
- SMTP configured for verification + reset emails
- Turnstile keys set for public-facing deployments
- Reverse proxy serves valid TLS certs
- Postgres dump scheduled off-host
- Log retention and backup policy covers
audit_log,api_log,access_log, and filesystemscribe-logs
CI / CD
On push (and tag push), these workflows run (depending on runner/site):
| Workflow | Output | Runtime |
|---|---|---|
.forgejo/workflows/android-apk.yml |
Signed APK attached to the Forgejo release, plus optional Google Play internal track upload | ~8 min |
docker-publish.yml |
Multi-arch image (amd64 + arm64 via native runners) on Docker Hub | ~4 min |
build-apk.yml |
Legacy TWA APK (optional second artifact) | ~2 min |
Triggered by auto-version.yml (reads commit messages, bumps + tags via
RELEASE_PAT) or manually via Actions → Version bump & release or
scripts/release.sh X.Y.Z --push.
Ports
| Service | Internal | External default |
|---|---|---|
| App | 3000 | 127.0.0.1:3552 |
| Postgres | 5432 | not exposed |
| Redis | 6379 | not exposed |
Change the app's external port by editing the ports: mapping in
docker-compose.yml.
Log destinations
- Container stdout (
docker compose logs -f pediatric-scribe). - Filesystem
data/logs/YYYY-MM-DD.log(JSONL, one line per event). - Postgres tables
audit_log,api_log,access_log— batched writes viasrc/utils/auditQueue.js, drained on SIGTERM. - Loki (if
LOKI_URLset) — pushed fire-and-forget per event.
A central Prometheus/Loki/Grafana stack can also scrape GET /metrics and collect Docker logs with Promtail. Keep direct Loki push enabled only for structured application events that are useful for compliance and operations.
Auto-cleanup
| Target | Policy | Frequency |
|---|---|---|
saved_encounters |
Delete where expires_at < NOW(). Default 7 days (configurable via site.auto_delete_days). |
Hourly + 10 s after startup |
audio_backups |
Delete where expires_at < NOW() (24 h default). |
Same schedule |
Graceful shutdown
server.js handles SIGTERM and SIGINT:
- Close HTTP listener (new connections refused, in-flight finish).
- Drain
src/utils/auditQueue.js(flush any pending audit/api/access writes). pool.end()— close Postgres pool cleanly.
9-second hard deadline — Docker sends SIGKILL after 10 s by default. Prevents
in-flight note writes from being truncated on docker restart.