Deployment Guide
Deploy Riven in production with Docker, environment variables, and reverse proxies
Deployment Guide
This guide covers production deployment of Riven using Docker, environment variables, and reverse proxies.
Prerequisites
- Operating System: Linux (Ubuntu, Debian, Fedora, Arch, etc.) or Windows WSL2
- Docker: Version 20.10+ with Docker Compose
- Hardware:
- CPU: 2+ cores recommended
- RAM: 4GB minimum, 8GB+ recommended
- Disk: Fast SSD preferred for database and cache
- Network: Stable internet connection
Linux Only
Riven only supports Linux-based systems. On Windows, use WSL2. MacOS is not officially supported due to FUSE limitations.
Docker Deployment
Complete docker-compose.yml
Here's a complete production-ready docker-compose.yml:
services:
# Riven Frontend
riven-frontend:
image: spoked/riven-frontend:latest
container_name: riven-frontend
restart: unless-stopped
ports:
- "3000:3000"
environment:
- PUID=1000
- PGID=1000
- TZ=America/New_York
- ORIGIN=http://localhost:3000 # Change to your domain
- BACKEND_URL=http://riven:8080
- DIALECT=postgres
- DATABASE_URL=postgres://postgres:postgres@riven-db/riven
depends_on:
riven:
condition: service_healthy
volumes:
- ./riven-frontend/config:/riven/config
networks:
- riven-network
# Riven Backend
riven:
image: spoked/riven:latest
container_name: riven
restart: unless-stopped
ports:
- "8080:8080"
cap_add:
- SYS_ADMIN
security_opt:
- apparmor:unconfined
devices:
- /dev/fuse
shm_size: 2g # Increase if using /dev/shm for cache
environment:
- PUID=1000
- PGID=1000
- TZ=America/New_York
# Database
- RIVEN_FORCE_ENV=true
- RIVEN_DATABASE_HOST=postgresql+psycopg2://postgres:postgres@riven-db/riven
# VFS Configuration
- RIVEN_FILESYSTEM_MOUNT_PATH=/mount
- RIVEN_FILESYSTEM_SEPARATE_ANIME_DIRS=false
- RIVEN_FILESYSTEM_CACHE_DIR=/dev/shm/riven-cache
- RIVEN_FILESYSTEM_CACHE_MAX_SIZE_MB=10240
- RIVEN_FILESYSTEM_CACHE_EVICTION=LRU
- RIVEN_FILESYSTEM_CACHE_TTL_SECONDS=7200
- RIVEN_FILESYSTEM_CACHE_METRICS=true
- RIVEN_FILESYSTEM_CHUNK_SIZE_MB=8
- RIVEN_FILESYSTEM_FETCH_AHEAD_CHUNKS=4
# Updaters
- RIVEN_UPDATER_INTERVAL=120
- RIVEN_LIBRARY_PATH=/mount
# Downloaders
- RIVEN_DOWNLOADERS_REAL_DEBRID_ENABLED=false
- RIVEN_DOWNLOADERS_REAL_DEBRID_API_KEY=
# Content Services (configure as needed)
- RIVEN_CONTENT_OVERSEERR_ENABLED=false
# Scrapers (configure as needed)
- RIVEN_SCRAPERS_TORRENTIO_ENABLED=true
healthcheck:
test: curl -s http://localhost:8080 >/dev/null || exit 1
interval: 30s
timeout: 10s
retries: 10
volumes:
- ./riven/data:/riven/data
- /path/to/riven/mount:/mount:rshared,z
depends_on:
riven-db:
condition: service_healthy
networks:
- riven-network
# Database
riven-db:
image: postgres:17-alpine
container_name: riven-db
restart: unless-stopped
environment:
- PGDATA=/var/lib/postgresql/data/pgdata
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres # Change in production!
- POSTGRES_DB=riven
volumes:
- ./riven-db:/var/lib/postgresql/data/pgdata
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
networks:
- riven-network
# Optional: Media Server (Plex example)
plex:
image: plexinc/pms-docker:latest
container_name: plex
restart: unless-stopped
network_mode: host
environment:
- PUID=1000
- PGID=1000
- TZ=America/New_York
- VERSION=docker
volumes:
- ./plex/config:/config
- /path/to/riven/mount:/mount:rslave,z
# networks:
# - riven-network # Can't use with network_mode: host
networks:
riven-network:
driver: bridge
Environment Variables Reference
Core Settings
# Debug and Logging
RIVEN_DEBUG=INFO # DEBUG, INFO, WARNING, ERROR
RIVEN_TRACEMALLOC=false
# Database
RIVEN_FORCE_ENV=true
RIVEN_DATABASE_HOST=postgresql+psycopg2://user:password@host/database
Filesystem (VFS) Settings
# Mount Configuration
RIVEN_FILESYSTEM_MOUNT_PATH=/mount
RIVEN_FILESYSTEM_SEPARATE_ANIME_DIRS=false
# Cache Configuration
RIVEN_FILESYSTEM_CACHE_DIR=/dev/shm/riven-cache
RIVEN_FILESYSTEM_CACHE_MAX_SIZE_MB=10240
RIVEN_FILESYSTEM_CACHE_TTL_SECONDS=7200
RIVEN_FILESYSTEM_CACHE_EVICTION=LRU # or TTL
RIVEN_FILESYSTEM_CACHE_METRICS=true
# Streaming Configuration
RIVEN_FILESYSTEM_CHUNK_SIZE_MB=8
RIVEN_FILESYSTEM_FETCH_AHEAD_CHUNKS=4
See Filesystem (VFS) for detailed explanations.
Updater Settings
# General
RIVEN_UPDATER_INTERVAL=120
RIVEN_LIBRARY_PATH=/mount
# Plex
RIVEN_PLEX_ENABLED=true
RIVEN_PLEX_TOKEN=your_plex_token
RIVEN_PLEX_URL=http://plex:32400
# Jellyfin
RIVEN_JELLYFIN_ENABLED=false
RIVEN_JELLYFIN_API_KEY=
RIVEN_JELLYFIN_URL=http://jellyfin:8096
# Emby
RIVEN_EMBY_ENABLED=false
RIVEN_EMBY_API_KEY=
RIVEN_EMBY_URL=http://emby:8096
Downloader Settings
# Video Extensions
RIVEN_DOWNLOADERS_VIDEO_EXTENSIONS=["mp4","mkv","avi"]
# Proxy (optional)
RIVEN_DOWNLOADERS_PROXY_URL=
# Real-Debrid
RIVEN_DOWNLOADERS_REAL_DEBRID_ENABLED=true
RIVEN_DOWNLOADERS_REAL_DEBRID_API_KEY=your_api_key
# AllDebrid
RIVEN_DOWNLOADERS_ALL_DEBRID_ENABLED=false
RIVEN_DOWNLOADERS_ALL_DEBRID_API_KEY=
# TorBox
RIVEN_DOWNLOADERS_TORBOX_ENABLED=false
RIVEN_DOWNLOADERS_TORBOX_API_KEY=
Content Service Settings
# Overseerr
RIVEN_CONTENT_OVERSEERR_ENABLED=true
RIVEN_CONTENT_OVERSEERR_API_KEY=your_api_key
RIVEN_CONTENT_OVERSEERR_URL=http://overseerr:5055
RIVEN_CONTENT_OVERSEERR_USE_WEBHOOK=false
RIVEN_CONTENT_OVERSEERR_UPDATE_INTERVAL=60
# Plex Watchlist
RIVEN_CONTENT_PLEX_WATCHLIST_ENABLED=false
RIVEN_CONTENT_PLEX_WATCHLIST_RSS=["https://rss.plex.tv/..."]
RIVEN_CONTENT_PLEX_WATCHLIST_UPDATE_INTERVAL=60
# Mdblist
RIVEN_CONTENT_MDBLIST_ENABLED=false
RIVEN_CONTENT_MDBLIST_API_KEY=
RIVEN_CONTENT_MDBLIST_LISTS=[]
RIVEN_CONTENT_MDBLIST_UPDATE_INTERVAL=300
# Trakt
RIVEN_CONTENT_TRAKT_ENABLED=false
RIVEN_CONTENT_TRAKT_API_KEY=
RIVEN_CONTENT_TRAKT_UPDATE_INTERVAL=300
Scraper Settings
# Torrentio
RIVEN_SCRAPERS_TORRENTIO_ENABLED=true
RIVEN_SCRAPERS_TORRENTIO_URL=http://torrentio.strem.fun
RIVEN_SCRAPERS_TORRENTIO_FILTER=sort=qualitysize%7Cqualityfilter=480p,scr,cam
RIVEN_SCRAPERS_TORRENTIO_TIMEOUT=30
RIVEN_SCRAPERS_TORRENTIO_RATELIMIT=true
# Add similar patterns for:
# - Jackett
# - Prowlarr
# - Zilean
# - Comet
# - Mediafusion
Host Mount Setup
Before starting Docker containers, set up the host mount point:
# Create mount directory
sudo mkdir -p /path/to/riven/mount
# Make it a shared bind mount
sudo mount --bind /path/to/riven/mount /path/to/riven/mount
sudo mount --make-rshared /path/to/riven/mount
# Verify
findmnt -T /path/to/riven/mount -o TARGET,PROPAGATION
# Should output: shared or rshared
Make Persistent (systemd)
Create /etc/systemd/system/riven-mount.service
:
[Unit]
Description=Riven VFS Mount Preparation
After=local-fs.target
Before=docker.service
[Service]
Type=oneshot
ExecStart=/usr/bin/mount --bind /path/to/riven/mount /path/to/riven/mount
ExecStart=/usr/bin/mount --make-rshared /path/to/riven/mount
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
Enable it:
sudo systemctl daemon-reload
sudo systemctl enable --now riven-mount.service
Reverse Proxy Configuration
Nginx
server {
listen 80;
server_name riven.example.com;
# Redirect to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name riven.example.com;
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/privkey.pem;
# Frontend
location / {
proxy_pass http://localhost:3000;
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;
}
# Backend API
location /api/ {
proxy_pass http://localhost:8080;
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;
}
}
Important: Remove ORIGIN
environment variable from frontend when using reverse proxy!
Caddy
riven.example.com {
# Frontend
reverse_proxy /* localhost:3000
# Backend API
reverse_proxy /api/* localhost:8080
}
Caddy handles SSL automatically with Let's Encrypt!
Traefik
# docker-compose.yml additions
services:
riven-frontend:
labels:
- "traefik.enable=true"
- "traefik.http.routers.riven-frontend.rule=Host(`riven.example.com`)"
- "traefik.http.routers.riven-frontend.entrypoints=websecure"
- "traefik.http.routers.riven-frontend.tls.certresolver=letsencrypt"
- "traefik.http.services.riven-frontend.loadbalancer.server.port=3000"
# Remove ORIGIN env variable
riven:
labels:
- "traefik.enable=true"
- "traefik.http.routers.riven-api.rule=Host(`riven.example.com`) && PathPrefix(`/api`)"
- "traefik.http.routers.riven-api.entrypoints=websecure"
- "traefik.http.routers.riven-api.tls.certresolver=letsencrypt"
- "traefik.http.services.riven-api.loadbalancer.server.port=8080"
Security Hardening
Change Default Passwords
# In docker-compose.yml
riven-db:
environment:
- POSTGRES_PASSWORD=your_secure_password_here
riven:
environment:
- RIVEN_DATABASE_HOST=postgresql+psycopg2://postgres:your_secure_password_here@riven-db/riven
Set Custom API Key
# Generate a secure 32-character key
openssl rand -hex 16
# Add to docker-compose.yml
riven:
environment:
- API_KEY=your_generated_api_key_here
Firewall Rules
# Only allow necessary ports
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP (redirect)
sudo ufw allow 443/tcp # HTTPS
sudo ufw enable
# Docker containers should NOT expose ports directly
# Use reverse proxy as shown above
Backup & Recovery
Backup Script
#!/bin/bash
# backup-riven.sh
BACKUP_DIR="/path/to/backups"
DATE=$(date +%Y%m%d-%H%M%S)
# Stop containers (optional, for consistency)
docker-compose stop riven riven-db
# Backup database
docker exec riven-db pg_dump -U postgres riven | gzip > "$BACKUP_DIR/riven-db-$DATE.sql.gz"
# Backup Riven data
tar czf "$BACKUP_DIR/riven-data-$DATE.tar.gz" ./riven/data
# Start containers
docker-compose start riven riven-db
# Clean old backups (keep 7 days)
find "$BACKUP_DIR" -name "riven-*" -mtime +7 -delete
Restore from Backup
# Stop containers
docker-compose stop riven riven-db
# Restore database
gunzip < riven-db-backup.sql.gz | docker exec -i riven-db psql -U postgres riven
# Restore data
tar xzf riven-data-backup.tar.gz
# Start containers
docker-compose up -d
Monitoring
Health Checks
Riven includes built-in health checks:
# Check backend health
curl http://localhost:8080/health
# Check database
docker exec riven-db pg_isready -U postgres
Docker Logs
# Follow all logs
docker-compose logs -f
# Specific service
docker logs -f riven
# Last 100 lines
docker logs --tail 100 riven
Resource Usage
# Monitor container resources
docker stats riven riven-db riven-frontend
Scaling & High Availability
Single Instance (Most Users)
The standard docker-compose.yml is sufficient for most users.
Load Balancing (Advanced)
For high-traffic deployments:
- Multiple Riven instances: Run multiple backend containers
- Shared database: All instances connect to same PostgreSQL
- Load balancer: Use Nginx/Traefik to distribute traffic
- Shared storage: VFS mount must be shared across instances
Advanced Use Case
Most users don't need this. Only consider for large multi-user deployments.
Updating Riven
Pull Latest Images
# Pull new images
docker-compose pull
# Recreate containers
docker-compose up -d
# Clean old images
docker image prune -f
Check for Updates
# See what's changed
docker-compose pull
docker-compose up -d --no-deps riven
Troubleshooting Deployment
See the main Troubleshooting Guide for detailed solutions.
Common issues:
- FUSE not available → Install fuse3, load kernel module
- Mount propagation → Follow host mount setup above
- Database connection → Check credentials, network
- API key errors → Generate proper 32-character key
See Also
- Filesystem (VFS) - VFS configuration
- Troubleshooting - Common issues
- Architecture - How it all works
- Performance Tuning - Optimize your setup