Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/rivenmedia/riven-ts/llms.txt

Use this file to discover all available pages before exploring further.

Production deployments require a few additions beyond a local test install: pinned image versions, external access through a reverse proxy, media server integration with correct mount propagation, and a backup routine for your PostgreSQL database. This guide walks through each concern in order.

Requirements

Linux + FUSE

A Linux host with FUSE support and /dev/fuse available. Riven uses FUSE to mount its virtual file system and cannot run without SYS_ADMIN capability.

Docker Engine 24+

Docker Engine 24 or later with Compose V2. The docker compose (V2) command is required — the legacy docker-compose binary is not supported.

PostgreSQL 17

PostgreSQL 17 holds your entire library: media items, item requests, streams, and VFS file entries. Data loss here means re-scraping everything.

Redis 8

Redis 8 stores in-flight queue state. It is ephemeral by design — losing Redis data only interrupts active jobs, not your library.

Host Mount Setup

Riven mounts its VFS inside the container using FUSE, then shares that mount back to the host using rshared propagation. The host mount point must be configured with shared propagation before Docker starts, or the FUSE mount will not be visible outside the container. A systemd unit is the most reliable way to guarantee this survives reboots:
1

Create the systemd unit

/etc/systemd/system/riven-mount.service
[Unit]
Description=Make Riven data bind mount shared
After=local-fs.target
Before=docker.service

[Service]
Type=oneshot
ExecStart=/usr/bin/mount --bind /mnt/riven /mnt/riven
ExecStart=/usr/bin/mount --make-rshared /mnt/riven
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
2

Enable the unit and verify propagation

sudo mkdir -p /mnt/riven
sudo systemctl daemon-reload
sudo systemctl enable --now riven-mount.service

# Must print "shared"
findmnt -o TARGET,PROPAGATION /mnt/riven
3

Create data directories owned by UID 1000

Riven runs as UID/GID 1000. The logs and data directories must be owned by that user or the container will fail to start.
mkdir -p logs data
sudo chown -R 1000:1000 logs data

Media Server Integration

Add your media server to the same docker-compose.yml. Riven publishes its FUSE mount with rshared; consumers (Plex, Jellyfin) subscribe to it with rslave.
The media server must start after Riven has mounted the VFS. If it scans an empty mount it will mark the library as unavailable. Use depends_on with condition: service_started to enforce this.
jellyfin:
  image: jellyfin/jellyfin:latest
  restart: unless-stopped
  ports:
    - 8096:8096
  volumes:
    - ./docker-data/jellyfin/config:/config
    - ./docker-data/jellyfin/cache:/cache
    - ${HOST_VFS_MOUNT_PATH}:/mount:rslave
  healthcheck:
    test: curl --connect-timeout 15 --silent --show-error --fail http://127.0.0.1:8096/
    interval: 1m00s
    timeout: 15s
    retries: 3
    start_period: 1m00s
  depends_on:
    riven:
      condition: service_started
Set RIVEN_PLUGIN_SETTING__REPO_PLUGIN_PLEX__plexLibraryPath (or the Jellyfin equivalent) to the container-side path — /mount in the examples above — not the host path /mnt/riven.

Reverse Proxy Setup

Riven serves its GraphQL API on port 3000. Before putting a proxy in front of it, you must bind the server to all interfaces:
RIVEN_SETTING__gqlHost="0.0.0.0"
Without this, gqlHost defaults to localhost and the API only listens on the loopback interface inside the container, so every proxied request is refused.
The GraphQL API has no built-in authentication. Do not expose it to the public internet without putting authentication in front of it at the proxy layer.
labels:
  - traefik.enable=true
  - traefik.http.routers.riven.rule=Host(`riven.example.com`)
  - traefik.http.routers.riven.entrypoints=websecure
  - traefik.http.routers.riven.tls=true
  - traefik.http.routers.riven.tls.certresolver=leresolver
  - traefik.http.services.riven.loadbalancer.server.port=3000

Version Pinning

The :main tag tracks the latest build of the default branch and can change under you at any docker compose pull. For production, pin a specific release:
docker-compose.yml
services:
  riven:
    image: ghcr.io/rivenmedia/riven-ts:1.2.3
Available tags are listed on the GitHub Container Registry package page. There is no latest tag.

Complete Production Compose File

A full production-ready docker-compose.yml with PostgreSQL and Redis health checks:
docker-compose.yml
services:
  riven:
    image: ghcr.io/rivenmedia/riven-ts:1.2.3   # pin your version
    container_name: riven
    restart: unless-stopped
    tty: true
    cap_add:
      - SYS_ADMIN
    security_opt:
      - apparmor:unconfined
    devices:
      - /dev/fuse
    env_file: .env.riven
    ports:
      - 3000:3000
    volumes:
      - ./docker-data/riven/logs:/app/logs
      - ./docker-data/riven/riven-ranking-config.json:/app/riven-ranking-config.json
      - ${HOST_VFS_MOUNT_PATH}:/mount:rshared,z
    depends_on:
      riven-db:
        condition: service_healthy
      riven-cache:
        condition: service_healthy

  riven-db:
    image: postgres:17-alpine
    container_name: riven-db
    restart: unless-stopped
    environment:
      POSTGRES_USER: riven
      POSTGRES_PASSWORD: CHANGEME
      POSTGRES_DB: riven
    volumes:
      - riven-db:/var/lib/postgresql/data/pgdata
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U riven"]
      interval: 5s
      timeout: 5s
      retries: 5

  riven-cache:
    image: redis:8-alpine
    container_name: riven-cache
    restart: unless-stopped
    command: redis-server --maxmemory-policy noeviction --appendonly yes
    volumes:
      - ./docker-data/riven-cache:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 10

volumes:
  riven-db:

Updating Riven

1

Pull the new image

docker compose pull
2

Restart with the new image

docker compose up -d
3

Verify the new version is running

docker compose ps
docker compose logs --tail=50 riven
Database migrations run automatically on startup. Watch the logs to confirm they complete successfully.

PostgreSQL Backups

PostgreSQL holds your entire library — media items, requests, streams, and VFS file entries. Back it up regularly.
1

Create a compressed dump

docker compose exec riven-db pg_dump -U riven riven \
  | gzip > riven-backup-$(date +%Y%m%d).sql.gz
2

Verify the backup

gunzip -c riven-backup-$(date +%Y%m%d).sql.gz | head -20
3

Restore from backup

gunzip -c riven-backup-20240101.sql.gz \
  | docker compose exec -T riven-db psql -U riven riven
Keep your .env.riven file and the generated ranking config (./docker-data/riven/riven-ranking-config.json) backed up alongside your database dumps. Logs are disposable and Redis only holds transient queue state — neither needs to be backed up.

Health Checks and Monitoring

GraphQL endpoint check

Confirm the API is responding after each deployment:
curl -s -X POST http://localhost:3000 \
  -H 'content-type: application/json' \
  -d '{"query":"{ __typename }"}' | jq .

Container status

Check that all three containers are healthy:
docker compose ps
All services should show healthy in the STATUS column.

Log tailing

Stream Riven’s logs in real time:
docker compose logs -f riven
Set RIVEN_SETTING__logLevel="warn" in production to reduce noise.

Mount verification

Verify the VFS is mounted and serving files:
ls /mnt/riven
findmnt -o TARGET,PROPAGATION /mnt/riven

Build docs developers (and LLMs) love