Deployment Guide¶
This guide covers deploying Metricis to production environments.
Overview¶
Metricis consists of four main components that need to be deployed:
- Server (FastAPI backend) - Python application
- Portal (Researcher admin) - Static React app
- Patient Portal (Patient/caregiver) - Static React app
- Client (Assessment interface) - Static jsPsych app
Additionally, you'll need: - PostgreSQL database - Redis for session storage and Celery - Celery workers for background tasks
Prerequisites¶
- Domain name with SSL certificate
- PostgreSQL 15+ database
- Redis 5.0+ instance
- SMTP server for email notifications (optional)
- Twilio account for SMS notifications (optional)
- Firebase project for push notifications (optional)
Environment Configuration¶
Server Environment Variables¶
Create a .env file in the server/ directory:
# Application
ENVIRONMENT=production
DEBUG=false
LOG_LEVEL=info
# Database
DATABASE_URL=postgresql+asyncpg://user:password@db-host:5432/metricis
# Security (REQUIRED - generate with: python -c "import secrets; print(secrets.token_urlsafe(32))")
JWT_SECRET_KEY=your-secret-key-here
SESSION_SECRET_KEY=your-session-secret-here
# Redis
SESSION_STORAGE_BACKEND=redis
REDIS_URL=redis://redis-host:6379/0
# CORS - List all frontend domains
ALLOWED_ORIGINS=https://portal.metricis.app,https://app.metricis.app,https://assess.metricis.app
# Rate Limiting
RATE_LIMIT_PER_MINUTE=60
AUTH_RATE_LIMIT_PER_MINUTE=10
# REDCap (if using)
REDCAP_TOKEN_SITE1=your-redcap-api-token
REDCAP_TOKEN_SITE2=another-redcap-token
# Notifications (optional)
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=noreply@metricis.app
SMTP_PASSWORD=your-smtp-password
TWILIO_ACCOUNT_SID=your-twilio-sid
TWILIO_AUTH_TOKEN=your-twilio-token
TWILIO_FROM_PHONE=+15551234567
FIREBASE_CREDENTIALS_PATH=/path/to/firebase-credentials.json
Frontend Environment Variables¶
Portal (.env.production)¶
Patient Portal (.env.production)¶
Client (.env.production)¶
Deployment Options¶
Option 1: Docker Compose (Recommended for Single Server)¶
1. Build and Run with Docker Compose¶
# docker-compose.prod.yml
version: '3.8'
services:
db:
image: postgres:15
environment:
POSTGRES_DB: metricis
POSTGRES_USER: metricis
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
restart: always
redis:
image: redis:7-alpine
restart: always
volumes:
- redis_data:/data
server:
build:
context: ./server
dockerfile: Dockerfile.prod
environment:
DATABASE_URL: postgresql+asyncpg://metricis:${DB_PASSWORD}@db:5432/metricis
REDIS_URL: redis://redis:6379/0
JWT_SECRET_KEY: ${JWT_SECRET_KEY}
SESSION_SECRET_KEY: ${SESSION_SECRET_KEY}
depends_on:
- db
- redis
restart: always
celery_worker:
build:
context: ./server
dockerfile: Dockerfile.prod
command: celery -A app.celery_app worker -Q reminders -l info
environment:
DATABASE_URL: postgresql+asyncpg://metricis:${DB_PASSWORD}@db:5432/metricis
REDIS_URL: redis://redis:6379/0
depends_on:
- db
- redis
restart: always
celery_beat:
build:
context: ./server
dockerfile: Dockerfile.prod
command: celery -A app.celery_app beat -l info
environment:
DATABASE_URL: postgresql+asyncpg://metricis:${DB_PASSWORD}@db:5432/metricis
REDIS_URL: redis://redis:6379/0
depends_on:
- db
- redis
restart: always
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
- ./portal/dist:/usr/share/nginx/html/portal
- ./patient-portal/dist:/usr/share/nginx/html/app
- ./client/dist:/usr/share/nginx/html/assess
depends_on:
- server
restart: always
volumes:
postgres_data:
redis_data:
2. Create Nginx Configuration¶
# nginx.conf
upstream api {
server server:8000;
}
server {
listen 80;
server_name portal.metricis.app app.metricis.app assess.metricis.app api.metricis.app;
return 301 https://$server_name$request_uri;
}
# Portal (Researcher Admin)
server {
listen 443 ssl http2;
server_name portal.metricis.app;
ssl_certificate /etc/nginx/ssl/portal.crt;
ssl_certificate_key /etc/nginx/ssl/portal.key;
root /usr/share/nginx/html/portal;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://api;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
# Patient Portal
server {
listen 443 ssl http2;
server_name app.metricis.app;
ssl_certificate /etc/nginx/ssl/app.crt;
ssl_certificate_key /etc/nginx/ssl/app.key;
root /usr/share/nginx/html/app;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
# Assessment Client
server {
listen 443 ssl http2;
server_name assess.metricis.app;
ssl_certificate /etc/nginx/ssl/assess.crt;
ssl_certificate_key /etc/nginx/ssl/assess.key;
root /usr/share/nginx/html/assess;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
# API Server
server {
listen 443 ssl http2;
server_name api.metricis.app;
ssl_certificate /etc/nginx/ssl/api.crt;
ssl_certificate_key /etc/nginx/ssl/api.key;
location / {
proxy_pass http://api;
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;
}
}
3. Deploy¶
# Build frontend assets
npm run build
npm run build:portal
npm run build:patient-portal
# Start services
docker-compose -f docker-compose.prod.yml up -d
# Run migrations
docker-compose exec server alembic upgrade head
# Create admin user
docker-compose exec server python -m app.cli create-admin \
--email admin@metricis.app \
--password secure-password
Option 2: Separate Cloud Services¶
Server Deployment (Railway, Fly.io, or AWS)¶
- Build the server Docker image:
# server/Dockerfile.prod
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
- Deploy to Railway:
-
Set environment variables in Railway dashboard
-
Run migrations:
Frontend Deployment (Vercel, Netlify, or Cloudflare Pages)¶
- Build frontend apps:
- Deploy to Vercel:
# Install Vercel CLI
npm i -g vercel
# Deploy portal
cd portal
vercel --prod
# Deploy patient portal
cd patient-portal
vercel --prod
# Deploy client
cd client
vercel --prod
-
Configure environment variables in Vercel dashboard
-
Set up custom domains for each app
Option 3: Kubernetes¶
See k8s/ directory for Kubernetes manifests (not included in this guide).
Database Migrations¶
Rolling out a REDCap-managed sponsor study? This guide covers the generic platform deployment. The study-specific cutover (REDCap project config, token rotation, DET smoke tests, per-study admin assignment, post-cutover monitoring, failure-mode runbook) is in Sponsor-Study Cutover Checklist. Run this guide first, then walk that checklist for each sponsor study.
Production Migration Strategy¶
- Backup database before migration:
- Test migration on staging first:
- Run migration with zero downtime:
# Option 1: Blue-green deployment
# Deploy new version alongside old, migrate, then switch traffic
# Option 2: Rolling deployment
# Ensure migrations are backward compatible
alembic upgrade head
- Rollback if needed:
SSL/TLS Certificates¶
Using Let's Encrypt (Recommended)¶
# Install certbot
sudo apt-get install certbot python3-certbot-nginx
# Obtain certificates
sudo certbot --nginx -d portal.metricis.app
sudo certbot --nginx -d app.metricis.app
sudo certbot --nginx -d assess.metricis.app
sudo certbot --nginx -d api.metricis.app
# Auto-renewal (certbot sets up a cron job automatically)
sudo certbot renew --dry-run
Monitoring and Logging¶
Application Logging¶
Server logs are structured JSON using structlog:
# Log levels: DEBUG, INFO, WARNING, ERROR, CRITICAL
logger.info("user_login", user_id=user.id, ip_address=request.client.host)
Log Aggregation¶
Use a log aggregation service:
- Datadog - Full observability platform
- Sentry - Error tracking and performance monitoring
- CloudWatch (AWS) - AWS-native logging
- Google Cloud Logging (GCP) - GCP-native logging
Health Checks¶
# Server health
curl https://api.metricis.app/api/health
# Database health
curl https://api.metricis.app/api/health/db
# Redis health
curl https://api.metricis.app/api/health/redis
Uptime Monitoring¶
Use an uptime monitoring service: - UptimeRobot - Pingdom - StatusCake
Backup Strategy¶
Database Backups¶
# Daily backup script
#!/bin/bash
BACKUP_DIR=/backups
DATE=$(date +%Y%m%d_%H%M%S)
pg_dump -U metricis -h db-host metricis | gzip > $BACKUP_DIR/metricis_$DATE.sql.gz
# Retain last 30 days
find $BACKUP_DIR -name "metricis_*.sql.gz" -mtime +30 -delete
File Backups¶
If storing uploaded files (consent PDFs, etc.), back up the file storage:
# For S3
aws s3 sync s3://metricis-files /backups/files/
# For local filesystem
rsync -av /var/metricis/files/ /backups/files/
Security Checklist¶
- [ ] All secrets in environment variables (not in code)
- [ ] SSL/TLS certificates configured and auto-renewing
- [ ] Database connections encrypted
- [ ] Rate limiting enabled
- [ ] CORS properly configured (only allow your domains)
- [ ] Security headers set (CSP, HSTS, X-Frame-Options)
- [ ] Regular security updates applied
- [ ] Backups tested and verified
- [ ] Monitoring and alerting configured
- [ ] Firewall rules configured
- [ ] SSH key authentication (no password auth)
Scaling Considerations¶
Horizontal Scaling¶
- Server: Run multiple uvicorn workers or multiple server instances behind a load balancer
- Celery Workers: Add more workers to handle background tasks
- Database: Use read replicas for read-heavy workloads
- Redis: Use Redis Cluster for high availability
Vertical Scaling¶
- Increase CPU/RAM for database
- Increase uvicorn workers:
uvicorn app.main:app --workers 4
Troubleshooting¶
Server won't start¶
- Check environment variables are set
- Check database connection:
psql $DATABASE_URL - Check Redis connection:
redis-cli -u $REDIS_URL ping - Check logs:
docker-compose logs server
Frontend not loading¶
- Check VITE_API_URL is set correctly
- Check CORS settings on server
- Check SSL certificates are valid
- Check browser console for errors
Database migration failed¶
- Restore from backup
- Check migration file for syntax errors
- Manually fix database schema
- Re-run migration
Next Steps¶
- Development Guide - Local development setup
- Testing Guide - Testing practices
- Mobile Deployment - iOS/Android deployment
- Architecture Overview - System design