feat(streaming): add jellyfin stack

New Portainer stack: jellyfin, jellystat + postgres, wizarr and
jellyplex-watched. Bulk data (metadata, trickplay, cache, transcodes)
binds to /storage1; only SQLite config lands on the root disk.

Trickplay gets an explicit sub-bind because 10.11 moved it to
/config/data/trickplay, which is not relocatable from the UI. Transcodes
are disk-backed rather than tmpfs because jellyfin#16608 orphans segments
on the Remux/DirectStream jobs that are routine on this server.
This commit is contained in:
ginnoir
2026-08-05 13:17:48 -05:00
parent fea3a6b29f
commit de759345ec
2 changed files with 124 additions and 0 deletions
+93
View File
@@ -0,0 +1,93 @@
# streaming stack — Jellyfin and its satellites.
#
# Storage split is deliberate: SQLite config on the root SSD, all bulk data
# (metadata, trickplay, cache, transcodes) on /storage1. The root disk is only
# 194G and has filled twice; nothing here may grow unbounded on it.
#
# Trickplay gets an explicit sub-bind because Jellyfin 10.11 moved it to
# /config/data/trickplay and it is NOT relocatable from the UI.
#
# Transcodes are disk-backed, not tmpfs: jellyfin#16608 means Remux and
# DirectStream jobs finish before the segment cleaner's first check, orphaning
# segments. Plex already writes transcodes to virtiofs at a 48% transcode rate.
#
# Env convention: every service gets stack.env via env_file; var names there are
# exactly what the containers read. No ${VAR} interpolation, no Portainer UI
# env vars.
services:
jellyfin:
container_name: jellyfin
image: lscr.io/linuxserver/jellyfin:latest
restart: unless-stopped
networks: [streaming, edge]
env_file:
- stack.env
environment:
- JELLYFIN_PublishedServerUrl=https://jellyfin.ginnoir.com
volumes:
- /etc/localtime:/etc/localtime:ro
- /config/jellyfin:/config
- /storage1/labdata/jellyfin/metadata:/config/metadata
- /storage1/labdata/jellyfin/cache:/config/cache
- /storage1/labdata/jellyfin/trickplay:/config/data/trickplay
- /storage1/labdata/jellyfin/transcodes:/transcode
- /storage1/Movies:/data/movies:ro
- /storage1/TV:/data/tv:ro
- /storage1/Anime:/data/anime:ro
ports:
- "8096:8096"
jellystat-db:
container_name: jellystat-db
image: postgres:16-alpine
restart: unless-stopped
networks: [streaming]
env_file:
- stack.env
labels:
- "com.centurylabs.watchtower.enable=false"
volumes:
- /config/jellystat-db:/var/lib/postgresql/data
jellystat:
container_name: jellystat
image: cyfershepard/jellystat:latest
restart: unless-stopped
networks: [streaming, edge]
depends_on:
- jellystat-db
env_file:
- stack.env
volumes:
- /config/jellystat:/app/backend/backup-data
ports:
- "3009:3000"
wizarr:
container_name: wizarr
image: ghcr.io/wizarrrr/wizarr:latest
restart: unless-stopped
networks: [streaming, edge]
env_file:
- stack.env
volumes:
- /config/wizarr:/data/database
ports:
- "5690:5690"
jellyplex-watched:
container_name: jellyplex-watched
image: luigi311/jellyplex-watched:latest
restart: unless-stopped
networks: [streaming, edge]
env_file:
- stack.env
networks:
streaming:
name: streaming
driver: bridge
edge:
name: edge
external: true