This guide covers deploying Phentrieve using Docker and Docker Compose for self-hosted research environments.
- Overview
- Prerequisites
- Quick Start
- Pre-built Data Bundles
- Volume Permissions (Linux Only)
- Configuration
- Hosted Research Deployment
- Troubleshooting
- Security Considerations
- Upgrading from Previous Versions
Phentrieve provides Docker images with comprehensive security hardening for hosted research deployments:
- ✅ Non-root execution - API runs as UID 10001, frontend as UID 101
- ✅ Resource limits - CPU and memory constraints prevent resource exhaustion
- ✅ Read-only filesystems - Containers have immutable root filesystems
- ✅ Dropped capabilities - Minimal Linux capabilities (CAP_DROP: ALL)
- ✅ Security options - no-new-privileges, seccomp profiles
- ✅ Health checks - Built-in liveness and readiness probes
Docker Images:
ghcr.io/berntpopp/phentrieve/api:latest- FastAPI backendghcr.io/berntpopp/phentrieve/frontend:latest- Vue.js frontend (nginx)
Required:
- Docker 20.10+ (Install Docker)
- Docker Compose 2.0+ (Install Compose)
Recommended:
- 8GB+ RAM (for embedding models)
- 10GB+ disk space (for HPO data and indexes)
- Linux, macOS, or Windows with WSL2
Network Ports:
8000- API server (internal, proxied)8080- Frontend (nginx, proxied)
git clone https://github.com/berntpopp/phentrieve.git
cd phentrieveLinux users must run this to configure permissions for non-root containers:
sudo ./scripts/setup-docker-volumes.shmacOS/Windows users: Skip this step (Docker Desktop handles permissions automatically).
Create .env.docker file and symlink for Docker Compose:
# Copy template
cp .env.docker.template .env.docker
# Create symlink (Docker Compose reads .env by default)
ln -s .env.docker .env
# Edit with your values
nano .env.dockerMinimum required variables:
# Data directories
PHENTRIEVE_HOST_DATA_DIR=/path/to/data
PHENTRIEVE_HOST_HF_CACHE_DIR=/path/to/data/hf_cache
# API configuration
VITE_API_URL_PUBLIC=http://your-domain.com/api
# Network (for Nginx Proxy Manager integration)
NPM_SHARED_NETWORK_NAME=npm_proxy_networkNote: If using pre-built Docker images from GHCR (
ghcr.io/berntpopp/phentrieve/api:latest), HPO data and indexes are already included. Skip to Step 5.
For custom builds or volume-mounted data, prepare HPO data:
# Option 1: Using local Python installation
phentrieve data prepare # Downloads hp.json, creates hpo_data.db (~12MB)
phentrieve index build # Creates ChromaDB vector indexes
# Option 2: Using Docker
docker-compose run --rm phentrieve_api phentrieve data prepare
docker-compose run --rm phentrieve_api phentrieve index buildCommon Error: If you see HPO database not found, either use pre-built images or run phentrieve data prepare. See Pre-built Data Bundles for details.
# Pull pre-built images (recommended)
docker-compose pull
# Start in background
docker-compose up -d
# Check logs
docker-compose logs -f# Check container health
docker-compose ps
# Test API
curl http://localhost:8000/api/v1/health
# Test frontend
curl http://localhost:8080/healthDocker images can include pre-built HPO data and vector indexes, eliminating the need to run phentrieve data prepare and phentrieve index build after deployment. This reduces cold start time from ~15 minutes to ~2 minutes.
The API Dockerfile supports three data preparation modes via build arguments:
| Mode | Build Args | Use Case | Cold Start |
|---|---|---|---|
| Bundle Download (default) | BUNDLE_URL=<url> |
Production, fastest startup | ~1-2 min |
| Build from Scratch | BUNDLE_URL="" BUILD_INDEXES=true |
Custom models, CI/CD | ~15-20 min |
| External Volume | BUNDLE_URL="" |
Development, shared data | Instant (if data exists) |
The default image includes the BioLORD multi-vector bundle (~150MB), which provides the best retrieval performance (MRR@10: 0.922).
# Uses default multi-vector bundle - no build args needed
docker build -t phentrieve-api ./api
# Or specify a different bundle
docker build \
--build-arg BUNDLE_URL="https://github.com/berntpopp/phentrieve/releases/download/data-v2026-02-16/phentrieve-data-v2026-02-16-biolord-multivec.tar.gz" \
-t phentrieve-api ./apiAvailable bundles (from GitHub Releases):
phentrieve-data-v2026-02-16-biolord-multivec.tar.gz- BioLORD multivector, recommendedphentrieve-data-v2026-02-16-biolord.tar.gz- BioLORD single-vectorphentrieve-data-v2026-02-16-minimal.tar.gz- HPO SQLite database only
Build indexes during image creation. Useful for custom models or air-gapped environments.
docker build \
--build-arg BUNDLE_URL="" \
--build-arg BUILD_INDEXES=true \
--build-arg BUILD_MODEL="FremyCompany/BioLORD-2023-M" \
-t phentrieve-api ./apiNo embedded data - mount a volume containing pre-existing data at runtime.
# Build without embedded data
docker build --build-arg BUNDLE_URL="" -t phentrieve-api ./api
# Run with mounted data volume
docker run -v /path/to/data:/phentrieve_data_mount phentrieve-apiUse the CLI to create bundles for distribution:
# Create bundle with BioLORD model
phentrieve data bundle create --model "FremyCompany/BioLORD-2023-M" --output-dir ./bundles/
# List available bundle configurations
phentrieve data bundle listBundle contents:
manifest.json- Metadata, checksums, model infohpo_data.db- SQLite database (~12MB)indexes/- ChromaDB vector indexes
Phentrieve containers run as non-root users for security:
- API container:
phentrieveuser (UID 10001, GID 10001) - Frontend container:
nginxuser (UID 101, GID 101)
On Linux hosts, volume mounts require explicit permission setup because Docker strictly enforces UID/GID matching. On macOS and Windows, Docker Desktop handles this automatically.
| Platform | Permission Setup Required? | Notes |
|---|---|---|
| Linux | ✅ Yes | Run sudo ./scripts/setup-docker-volumes.sh |
| macOS | ❌ No | Docker Desktop handles it automatically |
| Windows (WSL2) | ❌ No | Docker Desktop handles it automatically |
# Linux only - run with sudo
sudo ./scripts/setup-docker-volumes.shWhat it does:
- Creates required directories (
indexes/,hf_cache/) - Sets ownership to UID 10001:10001
- Validates permissions
- Provides next steps
If you prefer manual setup or the script doesn't work:
# Create directories
mkdir -p /path/to/data/indexes
mkdir -p /path/to/data/hf_cache
# Set ownership (Linux only)
sudo chown -R 10001:10001 /path/to/data/indexes
sudo chown -R 10001:10001 /path/to/data/hf_cache
# Verify permissions
ls -la /path/to/data/
# Should show: drwxr-xr-x 10001 10001 indexes/
# drwxr-xr-x 10001 10001 hf_cache/The following volumes must be writable by UID 10001:
# docker-compose.yml
volumes:
# Main data directory - read-write for HPO database and indexes
# Contains: hpo_data.db (SQLite), hp.json, indexes/ (ChromaDB)
- ${PHENTRIEVE_HOST_DATA_DIR}:/phentrieve_data_mount:rw
# Hugging Face model cache (read-write)
- ${PHENTRIEVE_HOST_HF_CACHE_DIR}:/app/.cache/huggingface:rwWhy read-write for data directory?
hpo_data.db: HPO SQLite database generated byphentrieve data preparehp.json: Downloaded HPO ontology fileindexes/: ChromaDB vector indexes for semantic search
Security is maintained via non-root user execution, read-only container filesystem, and dropped capabilities.
Data Directories:
PHENTRIEVE_HOST_DATA_DIR=/path/to/data # Base data directory
PHENTRIEVE_HOST_HF_CACHE_DIR=/path/to/hf_cache # Hugging Face cache (optional, defaults to $DATA_DIR/hf_cache)API Configuration:
LOG_LEVEL_API=INFO # Logging level (DEBUG, INFO, WARN, ERROR)
VITE_API_URL_PUBLIC=http://your-domain.com/api # Public API URL for frontendNetwork Configuration:
NPM_SHARED_NETWORK_NAME=npm_proxy_network # External network for reverse proxyDefault resource limits (defined in docker-compose.yml):
API Container:
resources:
limits:
cpus: '4.0' # Maximum 4 CPU cores
memory: 8G # Maximum 8GB RAM
reservations:
cpus: '1.0' # Minimum 1 CPU core
memory: 4G # Minimum 4GB RAMFrontend Container:
resources:
limits:
cpus: '0.5' # Maximum 0.5 CPU cores
memory: 256M # Maximum 256MB RAM
reservations:
cpus: '0.1' # Minimum 0.1 CPU cores
memory: 64M # Minimum 64MB RAMCustomizing Limits:
Create docker-compose.override.yml:
services:
phentrieve_api:
deploy:
resources:
limits:
cpus: '8.0' # Increase for better performance
memory: 16GAPI Health Check:
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8000/api/v1/health || exit 1"]
interval: 30s # Check every 30 seconds
timeout: 10s # 10 second timeout
retries: 5 # 5 retries before marking unhealthy
start_period: 180s # 3 minute grace period for model loadingFrontend Health Check:
healthcheck:
# Using wget (not curl) and 127.0.0.1 (not localhost) for Alpine compatibility
test: ["CMD-SHELL", "wget -q --spider http://127.0.0.1:8080/health || exit 1"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10sPhentrieve is designed to work with Nginx Proxy Manager (NPM) for SSL termination and routing.
1. Create external network (if not exists):
docker network create npm_proxy_network2. Configure NPM proxy hosts:
API Proxy Host:
- Domain:
api.your-domain.com - Forward Hostname:
phentrieve_api - Forward Port:
8000 - Websockets: ✅ Enabled
- SSL: ✅ Request SSL certificate
Frontend Proxy Host:
- Domain:
your-domain.com - Forward Hostname:
phentrieve_frontend - Forward Port:
8080 - SSL: ✅ Request SSL certificate
3. Update environment:
# .env
VITE_API_URL_PUBLIC=https://api.your-domain.com/api
NPM_SHARED_NETWORK_NAME=npm_proxy_networkDevelopment build (local changes):
docker-compose -f docker-compose.yml -f docker-compose.dev.yml buildProduction build (from source):
docker-compose build --no-cacheMulti-platform build (for deployment to different architectures):
docker buildx build --platform linux/amd64,linux/arm64 \
-t ghcr.io/berntpopp/phentrieve/api:custom \
-f api/Dockerfile \
--push .Recommended backup strategy:
#!/bin/bash
# backup-phentrieve.sh
BACKUP_DIR="/backups/phentrieve/$(date +%Y%m%d)"
DATA_DIR="${PHENTRIEVE_HOST_DATA_DIR}"
mkdir -p "$BACKUP_DIR"
# Backup HPO database (critical)
cp "${DATA_DIR}/hpo_data.db" "${BACKUP_DIR}/"
# Backup vector indexes (large, but reproducible)
tar -czf "${BACKUP_DIR}/indexes.tar.gz" "${DATA_DIR}/indexes/"
# Backup configuration
cp .env "${BACKUP_DIR}/"
echo "Backup complete: ${BACKUP_DIR}"Restoration:
# Stop containers
docker-compose down
# Restore data
cp /backups/phentrieve/20250119/hpo_data.db /path/to/data/
tar -xzf /backups/phentrieve/20250119/indexes.tar.gz -C /path/to/data/
# Fix permissions (Linux)
sudo chown -R 10001:10001 /path/to/data/indexes
# Restart
docker-compose up -dContainer health:
# Check status
docker-compose ps
# Health check logs
docker inspect phentrieve_api | jq '.[0].State.Health'Resource usage:
# Live stats
docker stats phentrieve_api phentrieve_frontend
# Memory usage
docker exec phentrieve_api ps aux --sort=-%mem | headLogs:
# Follow all logs
docker-compose logs -f
# API only (last 100 lines)
docker-compose logs --tail=100 -f phentrieve_api
# Export logs
docker-compose logs --no-color > phentrieve-logs-$(date +%Y%m%d).txtError:
Error: [Errno 13] Permission denied: '/phentrieve_data_mount/indexes/...'
Solution (Linux):
# Run the setup script
sudo ./scripts/setup-docker-volumes.sh
# Or manually fix permissions
sudo chown -R 10001:10001 /path/to/data/indexes
sudo chown -R 10001:10001 /path/to/data/hf_cacheSolution (macOS/Windows): This error should not occur on macOS or Windows. If it does:
- Verify Docker Desktop is running
- Check volume mounts in
docker-compose.yml - Restart Docker Desktop
Check logs:
docker-compose logs phentrieve_apiCommon issues:
-
Out of memory during model loading:
# Increase memory limit in docker-compose.override.yml resources: limits: memory: 16G -
Missing HPO data (most common for fresh deployments):
# Prepare data and build indexes docker-compose run --rm phentrieve_api phentrieve data prepare docker-compose run --rm phentrieve_api phentrieve index buildError message:
HPO database not found: .../hpo_data.dbThis means the SQLite database hasn't been created. The
data preparecommand downloads the HPO ontology and generates the required database file. -
Port conflict:
# Check if ports in use sudo lsof -i :8000 sudo lsof -i :8080 # Change ports in docker-compose.yml ports: - "8001:8000" # API - "8081:8080" # Frontend
Symptoms: Container takes >5 minutes to become healthy
Solutions:
-
Increase health check start period:
healthcheck: start_period: 300s # 5 minutes
-
Pre-download models:
# Download models before starting docker-compose run --rm phentrieve_api python -c " from sentence_transformers import SentenceTransformer SentenceTransformer('FremyCompany/BioLORD-2023-M') "
-
Use persistent cache: Ensure
PHENTRIEVE_HOST_HF_CACHE_DIRis set and persistent.
Error: Frontend can't reach API
Check:
# Verify containers are on same network
docker network inspect phentrieve_internal_net
# Test API from frontend container
docker exec phentrieve_frontend wget -O- http://phentrieve_api:8000/api/v1/healthSolution:
Ensure both containers are on phentrieve_internal_net network.
Why it matters:
- Prevents container escape vulnerabilities
- Limits damage if container is compromised
- Follows principle of least privilege
- Required for Kubernetes security policies
Implementation:
services:
phentrieve_api:
user: "10001:10001" # phentrieve:phentrieveWhy it matters:
- Prevents malicious code from modifying container
- Makes container immutable
- Detects unauthorized file changes
Writable directories (via tmpfs):
tmpfs:
- /tmp:uid=10001,gid=10001,mode=1777,size=1G
- /app/.cache:uid=10001,gid=10001,mode=0755,size=2GWhy it matters:
- Prevents resource exhaustion attacks
- Ensures fair resource allocation
- Protects host system
Monitoring:
# Check if limits are being hit
docker stats phentrieve_api
# Look for container restarts (OOM)
docker inspect phentrieve_api | jq '.[0].State.Restarted'Never commit secrets to git:
# Use .env for local development
echo ".env" >> .gitignore
# Use Docker secrets for production
docker secret create phentrieve_db_password ./db_password.txtIn docker-compose.yml:
services:
phentrieve_api:
secrets:
- db_password
secrets:
db_password:
external: trueAlways use HTTPS in production:
- Terminate SSL at reverse proxy (Nginx Proxy Manager)
- Use Let's Encrypt for free certificates
- Enable HTTP to HTTPS redirect
- Set HSTS headers
This section covers breaking changes introduced with the security-hardened Docker configuration and how to migrate existing deployments.
| Change | Before | After | Impact |
|---|---|---|---|
| Container User | root (UID 0) | non-root (UID 10001/101) | Data permissions must be updated |
| Frontend Port | 80 | 8080 | Reverse proxy config needs update |
| HPO Data Format | JSON + pickle files | SQLite database | Re-run phentrieve data prepare |
| CORS Config | Hardcoded/different | Via api.yaml or env var |
Add production URL to CORS |
| Environment File | Various | .env.docker with .env symlink |
Update env file setup |
docker-compose down# Backup current data (optional but recommended)
cp -r /path/to/data /path/to/data.backupdocker-compose pullThe new containers run as non-root users. Existing data owned by root must be updated:
# Fix permissions for API container (UID 10001)
sudo chown -R 10001:10001 /path/to/data/indexes
sudo chown -R 10001:10001 /path/to/data/hf_cache
# Verify permissions
ls -la /path/to/data/
# Expected: drwxr-xr-x 10001 10001 indexes/
# drwxr-xr-x 10001 10001 hf_cache/The recommended setup now uses a .env symlink:
# Create/update .env.docker from template
cp .env.docker.template .env.docker
# Edit with your settings
nano .env.docker
# Create symlink for Docker Compose
ln -sf .env.docker .envIf you see errors about missing hpo_data.db or incompatible data format:
# Remove old pickle files (no longer used)
rm -f /path/to/data/hpo_graph_data.pkl
rm -f /path/to/data/hpo_label_map.pkl
# Regenerate HPO database
docker-compose run --rm phentrieve_api phentrieve data prepareAdd your production frontend URL to CORS configuration:
Option A: Environment variable (recommended)
# In .env.docker
CORS_EXTRA_ORIGINS=https://your-frontend.example.comOption B: Edit api.yaml
# api/api.yaml
cors:
allowed_origins:
- "http://localhost:5734"
- "https://your-frontend.example.com"If using Nginx Proxy Manager or similar, update the frontend proxy:
Old configuration:
- Forward Port:
80
New configuration:
- Forward Port:
8080
docker-compose up -d
# Verify health
docker-compose ps
docker-compose logs -f --tail=50# Test API health
curl http://localhost:8000/api/v1/health
# Test frontend
curl http://localhost:8080/health
# Test a query (from your frontend URL)
# Should not show CORS errors in browser consoleError:
attempt to write a readonly database
DenseRetriever: Failed to connect to Chroma collection
Solution:
sudo chown -R 10001:10001 /path/to/data/indexes
docker-compose restart phentrieve_apiError:
OPTIONS /api/v1/query/ HTTP/1.1" 400 Bad Request
Solution:
Add production URL to CORS_EXTRA_ORIGINS in .env.docker:
CORS_EXTRA_ORIGINS=https://your-frontend.example.comError:
/bin/sh: curl: not found
Solution:
This is fixed in the latest version. The health check now uses wget instead of curl. Pull the latest images:
docker-compose pull
docker-compose up -dError:
WARN: The "PHENTRIEVE_HOST_DATA_DIR" variable is not set.
Solution:
Ensure .env symlink exists and points to .env.docker:
ln -sf .env.docker .envOr use explicit env file:
docker-compose --env-file .env.docker up -d- Docker Security Best Practices
- Nginx Proxy Manager Documentation
- Phentrieve API Documentation
- Phentrieve GitHub Repository
Issues:
Logs: When reporting issues, include:
# System info
docker version
docker-compose version
uname -a
# Container logs
docker-compose logs --tail=100
# Container health
docker-compose psCommunity:
- Check existing GitHub issues for solutions
- Include detailed error messages and logs in new issues