ClinicalPilot can be deployed using Docker for consistent, reproducible deployments across environments.
Docker files are not included in the current repository. This guide shows how to create them from scratch.
Prerequisites
Docker 24.0+
Docker Compose 2.0+
16GB+ RAM (32GB+ for local LLM)
Creating the Dockerfile
Create Dockerfile in the project root:
# Dockerfile
FROM python:3.11-slim
# Set working directory
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
poppler-utils \
tesseract-ocr \
libmagic1 \
curl \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements first (for layer caching)
COPY requirements.txt .
# Install Python dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Download spaCy model
RUN pip install https://github.com/explosion/spacy-models/releases/download/en_core_web_lg-3.7.1/en_core_web_lg-3.7.1-py3-none-any.whl
# Copy application code
COPY backend/ /app/backend/
COPY frontend/ /app/frontend/
COPY data/ /app/data/
# Create data directories
RUN mkdir -p /app/data/lancedb /app/data/drugbank
# Expose port
EXPOSE 8000
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -f http://localhost:8000/api/health || exit 1
# Run the application
CMD [ "uvicorn" , "backend.main:app" , "--host" , "0.0.0.0" , "--port" , "8000" , "--workers" , "4" ]
Creating docker-compose.yml
Create docker-compose.yml in the project root:
version : '3.9'
services :
clinicalpilot :
build :
context : .
dockerfile : Dockerfile
container_name : clinicalpilot
ports :
- "8000:8000"
environment :
# LLM Configuration
- OPENAI_API_KEY=${OPENAI_API_KEY}
- OPENAI_MODEL=gpt-4o
- OPENAI_FAST_MODEL=gpt-4o-mini
# Groq (AI Chat)
- GROQ_API_KEY=${GROQ_API_KEY}
- GROQ_MODEL=llama-3.3-70b-versatile
# PubMed
- NCBI_EMAIL=${NCBI_EMAIL}
- NCBI_API_KEY=${NCBI_API_KEY}
# Observability
- LANGCHAIN_TRACING_V2=${LANGCHAIN_TRACING_V2:-false}
- LANGSMITH_API_KEY=${LANGSMITH_API_KEY}
- LANGCHAIN_PROJECT=clinicalpilot
# Application Settings
- LOG_LEVEL=INFO
- CORS_ORIGINS=["*"]
- EMERGENCY_TIMEOUT_SEC=5
- MAX_DEBATE_ROUNDS=3
# Data Paths
- LANCEDB_PATH=/app/data/lancedb
- DRUGBANK_CSV_PATH=/app/data/drugbank/drugbank_vocabulary.csv
volumes :
# Persist LanceDB vector store
- lancedb_data:/app/data/lancedb
# Persist DrugBank data
- drugbank_data:/app/data/drugbank
# Optional: mount custom documents for RAG
- ./data/rag_documents:/app/data/rag_documents:ro
restart : unless-stopped
healthcheck :
test : [ "CMD" , "curl" , "-f" , "http://localhost:8000/api/health" ]
interval : 30s
timeout : 10s
retries : 3
start_period : 60s
# Optional: Redis for caching
redis :
image : redis:7-alpine
container_name : clinicalpilot-redis
ports :
- "6379:6379"
volumes :
- redis_data:/data
restart : unless-stopped
healthcheck :
test : [ "CMD" , "redis-cli" , "ping" ]
interval : 10s
timeout : 5s
retries : 3
# Optional: Nginx reverse proxy
nginx :
image : nginx:alpine
container_name : clinicalpilot-nginx
ports :
- "80:80"
- "443:443"
volumes :
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on :
- clinicalpilot
restart : unless-stopped
volumes :
lancedb_data :
drugbank_data :
redis_data :
Environment Variables
Create .env.docker for Docker-specific configuration:
# .env.docker
OPENAI_API_KEY = sk-...
GROQ_API_KEY = gsk_...
NCBI_EMAIL = [email protected]
NCBI_API_KEY = ...
LANGCHAIN_TRACING_V2 = false
LANGSMITH_API_KEY =
Do not commit .env.docker to Git. Add it to .gitignore.
Building and Running
Build the Image
This takes 5-10 minutes (downloads Python packages, spaCy model).
Start the Services
docker-compose --env-file .env.docker up -d
The -d flag runs in detached mode (background).
Check Logs
docker-compose logs -f clinicalpilot
Look for: INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8000
Verify Health
curl http://localhost:8000/api/health
Expected: { "status" : "ok" , "version" : "1.0.0" , "timestamp" : "..." }
Stopping and Removing
# Stop services
docker-compose down
# Stop and remove volumes (WARNING: deletes LanceDB data)
docker-compose down -v
Production Configuration
Multi-Stage Build (Smaller Image)
# Dockerfile.prod
FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txt
FROM python:3.11-slim
RUN apt-get update && apt-get install -y \
poppler-utils tesseract-ocr libmagic1 curl \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY backend/ /app/backend/
COPY frontend/ /app/frontend/
ENV PATH=/root/.local/bin:$PATH
EXPOSE 8000
CMD [ "uvicorn" , "backend.main:app" , "--host" , "0.0.0.0" , "--port" , "8000" , "--workers" , "4" ]
HTTPS with Nginx
Create nginx.conf:
events {
worker_connections 1024 ;
}
http {
upstream clinicalpilot {
server clinicalpilot:8000;
}
server {
listen 80 ;
server_name clinicalpilot.example.com;
return 301 https://$ server_name $ request_uri ;
}
server {
listen 443 ssl http2;
server_name clinicalpilot.example.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
proxy_pass http://clinicalpilot;
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 ;
}
location /ws/ {
proxy_pass http://clinicalpilot;
proxy_http_version 1.1 ;
proxy_set_header Upgrade $ http_upgrade ;
proxy_set_header Connection "upgrade" ;
}
}
}
Add SSL certificates to ssl/ directory:
mkdir ssl
# Copy your fullchain.pem and privkey.pem to ssl/
Docker Compose with Ollama (Local LLM)
Add Ollama service:
services :
ollama :
image : ollama/ollama:latest
container_name : clinicalpilot-ollama
ports :
- "11434:11434"
volumes :
- ollama_data:/root/.ollama
restart : unless-stopped
clinicalpilot :
# ... existing config ...
environment :
- USE_LOCAL_LLM=true
- OLLAMA_BASE_URL=http://ollama:11434
- OLLAMA_MODEL=medgemma2:9b
depends_on :
- ollama
volumes :
ollama_data :
Pull MedGemma model:
docker exec -it clinicalpilot-ollama ollama pull medgemma2:9b
Persistent Data
Docker volumes persist data across container restarts:
Volume Purpose Backup? lancedb_dataVector store Yes (daily) drugbank_dataDrug interaction data Yes (weekly) redis_dataCache (optional) No ollama_dataLLM models No (re-download if lost)
Backing Up Volumes
# Backup LanceDB
docker run --rm -v lancedb_data:/data -v $( pwd ) :/backup \
alpine tar czf /backup/lancedb_backup.tar.gz -C /data .
# Restore LanceDB
docker run --rm -v lancedb_data:/data -v $( pwd ) :/backup \
alpine tar xzf /backup/lancedb_backup.tar.gz -C /data
Monitoring with Prometheus
Add Prometheus and Grafana:
services :
prometheus :
image : prom/prometheus:latest
container_name : clinicalpilot-prometheus
ports :
- "9090:9090"
volumes :
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
restart : unless-stopped
grafana :
image : grafana/grafana:latest
container_name : clinicalpilot-grafana
ports :
- "3000:3000"
volumes :
- grafana_data:/var/lib/grafana
environment :
- GF_SECURITY_ADMIN_PASSWORD=admin
restart : unless-stopped
volumes :
prometheus_data :
grafana_data :
Create prometheus.yml:
global :
scrape_interval : 15s
scrape_configs :
- job_name : 'clinicalpilot'
static_configs :
- targets : [ 'clinicalpilot:8000' ]
Troubleshooting
Container Fails to Start
# Check logs
docker-compose logs clinicalpilot
# Check container status
docker ps -a
# Inspect container
docker inspect clinicalpilot
Health Check Failing
# Check health status
docker inspect clinicalpilot | grep -A 10 Health
# Test health endpoint manually
docker exec clinicalpilot curl -f http://localhost:8000/api/health
Out of Memory
Increase Docker memory limit:
# Docker Desktop: Settings → Resources → Memory → 16GB+
# Or in docker-compose.yml:
services:
clinicalpilot:
mem_limit: 16g
mem_reservation: 8g
Volume Permissions
If you see permission errors:
# Fix ownership (use container user ID)
docker run --rm -v lancedb_data:/data alpine chown -R 1000:1000 /data
CI/CD with Docker
GitHub Actions example:
# .github/workflows/docker.yml
name : Docker Build and Push
on :
push :
branches : [ main ]
jobs :
build :
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v3
- name : Build Docker image
run : docker build -t clinicalpilot:latest .
- name : Run smoke tests
run : |
docker run -d -p 8000:8000 --name test clinicalpilot:latest
sleep 30
curl -f http://localhost:8000/api/health
- name : Push to registry
run : |
echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
docker tag clinicalpilot:latest myregistry/clinicalpilot:${{ github.sha }}
docker push myregistry/clinicalpilot:${{ github.sha }}
Next Steps
Production Deployment Deploy Docker containers to production
HIPAA Compliance Security and compliance for Docker deployments