added jellyfin

This commit is contained in:
Goyban
2026-08-18 15:03:27 +02:00
parent 28bcc8f9fb
commit 369bf69abf
13 changed files with 628 additions and 34 deletions
+54
View File
@@ -0,0 +1,54 @@
# Copy to .env and adjust. .env is git-ignored; this file is not.
# Nothing here is secret, but keep it that way.
# ─── Identity ───────────────────────────────────────────────────────
# These three are the same for every service on the machine, so compose.yaml
# already defaults them to 1000/1000/Etc/UTC. Three places can set them, in
# increasing priority:
#
# 1. the defaults in compose.yaml (a fresh clone just works)
# 2. this file (per-service)
# 3. exported in your shell (global, wins over this file)
#
# If you keep a global `export PUID=...` in ~/.profile, leave these commented.
#
# The host user that should own everything Jellyfin writes: `id -u` / `id -g`.
# This is the setting that bites hardest when it's wrong: the container starts
# fine and then can't write its database, or writes it as root and you can no
# longer touch the files from the host.
#PUID=1000
#PGID=1000
# Affects timestamps in logs and scheduled-task times.
#TZ=Europe/Paris
# ─── Paths ──────────────────────────────────────────────────────────
# Jellyfin's own state: database, metadata, artwork, subtitles, logs.
# Small enough to live next to the compose file — see the README. The
# repo's .gitignore keeps it out of git either way. Defaults to ./config.
#CONFIG_PATH=./config
# Your main media library. REQUIRED — compose refuses to start without it,
# rather than silently mounting nothing.
MEDIA_PATH=/srv/media
# Extra libraries on other disks. Uncomment the matching lines in
# compose.yaml too, otherwise these do nothing.
#MEDIA_PATH_2=/mnt/disk2/tv
#MEDIA_PATH_3=/mnt/scratch/downloads
# ─── Ports ──────────────────────────────────────────────────────────
# Defaulted in compose.yaml; set here only to move them.
#PORT=8096
#DISCOVERY_PORT=7359
# ─── Hardware transcoding ───────────────────────────────────────────
# GID of the host group that owns /dev/dri/renderD128. Host-specific:
#
# getent group render | cut -d: -f3
#
# It varies wildly between distros and installs (105, 992, 993, 989 are
# all real answers I've seen). Do not copy the number below — look it up.
# REQUIRED while the hardware-transcoding block is present in compose.yaml;
# delete that block and this line together if you transcode on the CPU.
RENDER_GID=105
+110
View File
@@ -0,0 +1,110 @@
# Jellyfin
Media server. Reads films and TV off disk and streams them to whatever is in
the room — no account, no subscription, nothing phoning home.
## Quick start
```bash
cp .env.example .env
$EDITOR .env # MEDIA_PATH and RENDER_GID are required
docker compose up -d
```
Open `http://<host>:8096` and point your libraries at `/media` — the path
*inside* the container, whatever `MEDIA_PATH` is on the host.
## Ports
| Port | Proto | Purpose | |
|------|-------|---------|---|
| 8096 | http | Web UI and client API | published |
| 7359 | udp | LAN auto-discovery | published |
| 8920 | https | Jellyfin's own TLS listener | off — TLS belongs at the proxy |
| 1900 | udp | DLNA / SSDP | off — uncomment if you use DLNA |
## Why it looks like this
**The LinuxServer image.** I started with it years ago, it has worked every day
since, and I'm not changing a base image to fix nothing. The better reason: I
run several of their images and they all speak the same dialect —
`PUID`/`PGID`/`TZ`, `/config`, s6, one rebuild cadence. Learn one, learn the
family. Jellyfin's official image is also fine; it just doesn't do the
`PUID`/`PGID` dance, so ownership works differently.
**`/config` lives next to the compose file**, breaking this repo's own
"data outside the repo" rule on purpose. It's Jellyfin's database, metadata and
artwork — 988M after three years here. Under a gigabyte, and keeping it beside
the compose file means the whole service is one `tar` from being moved.
`.gitignore` keeps it out of git. Note that transcode scratch files land under
`/config` and *can* spike several GB — move that path in **Dashboard → Playback**
if the disk is small.
**`PUID`/`PGID`/`TZ` carry defaults** (`${PUID:-1000}`) because they're the same
for every service on a box. Compose resolves them from the default, then `.env`,
then the ambient environment — so a global `export PUID=…`, or OpenMediaVault's
compose plugin, wins. That last one is why my own copy has bare `${PUID}`: OMV
supplies those three, so they're never empty *on my machine*. Without OMV they
would be, and Compose interpolates an empty string and starts anyway rather than
failing. Hence the defaults.
Values that can't be guessed use `${VAR:?message}` instead and abort:
```console
$ docker compose up -d
error while interpolating services.jellyfin.group_add.[]: required variable
RENDER_GID is missing a value: find yours with: getent group render | cut -d: -f3
```
**One `/media` mount.** My real server has three libraries on separate disks;
that's my risk appetite, not yours. Add a line per library if you need more.
Upstream's example splits `/data/tvshows` and `/data/movies` — fine on separate
disks, but if they share a filesystem, mounting the common parent is the better
habit: hardlinks only survive within one mount point inside the container.
Mount media `:ro` if you like — Jellyfin never writes to it.
**Hardware transcoding needs `devices` *and* `group_add`.** Passing
`/dev/dri/renderD128` in is half the job; the node is owned by a host group and
the container user has to be in it or the device opens and does nothing. The
catch is that `group_add` wants a numeric GID and it differs per host — mine is
`105`, on the laptop I wrote this on it's `992`. Copying someone's compose file
verbatim is exactly how you get silently broken acceleration.
```bash
getent group render | cut -d: -f3
```
Not on Intel? Delete both keys — CPU transcoding works, it just costs cores.
NVIDIA and AMD variants:
[LinuxServer's hardware acceleration docs](https://docs.linuxserver.io/images/docker-jellyfin/#hardware-acceleration).
**`restart: unless-stopped`, not `always`** — if I stop it deliberately, it
should stay stopped across a reboot.
## Gotchas
- **`:latest` is a choice.** Pin a tag if that makes you nervous. Back up
`/config` either way — that directory *is* your server.
- **Auto-discovery is LAN-only.** 7359/udp is broadcast; behind a proxy it does
nothing for you. Set `JELLYFIN_PublishedServerUrl` there instead.
- **On a subpath, set the base URL** under **Dashboard → Networking** first, or
the UI loads and every asset 404s. A subdomain avoids the question.
- **Verify hardware transcoding actually engaged.** Enabling VAAPI and it
*working* are different states — force a transcode and check
**Dashboard → Playback**. A wrong GID falls back to software silently.
## Exposing it
Publishes plain HTTP; nothing here is proxy-aware.
- [Caddy reverse proxy](../docs/reverse-proxy.md) — what I do.
- [Cloudflare Tunnel](../docs/cloudflare-tunnel.md) — **not for this one.**
Streaming video through a tunnel breaks Cloudflare's terms and performs badly.
- [Authentik SSO](../docs/authentik-sso.md) — works for the web UI, but native
clients can't handle a forward-auth login page.
## Links
- Jellyfin docs: <https://jellyfin.org/docs/>
- Hardware acceleration: [upstream](https://jellyfin.org/docs/general/administration/hardware-acceleration/) · [this image](https://docs.linuxserver.io/images/docker-jellyfin/#hardware-acceleration)
- Image: <https://docs.linuxserver.io/images/docker-jellyfin/>
+44
View File
@@ -0,0 +1,44 @@
services:
jellyfin:
image: lscr.io/linuxserver/jellyfin:latest
container_name: jellyfin
restart: unless-stopped
# Defaults make a fresh clone work with no .env at all. Anything you set
# -- in this service's .env, or exported globally in your shell -- wins.
environment:
- PUID=${PUID:-1000}
- PGID=${PGID:-1000}
- TZ=${TZ:-Etc/UTC}
# The address Jellyfin advertises to clients during auto-discovery. Worth
# setting behind a reverse proxy, where clients otherwise get handed the
# container's internal address and fail to connect.
#- JELLYFIN_PublishedServerUrl=https://jellyfin.example.com
volumes:
# Jellyfin's own state. Stays next to this file on purpose — see README.
- ${CONFIG_PATH:-./config}:/config
- "${MEDIA_PATH:?set MEDIA_PATH in .env to your media library}:/media"
# Extra libraries on other disks. One line per library; the path after
# the colon is what you browse to when adding the library in the UI.
#- ${MEDIA_PATH_2}:/media2
#- ${MEDIA_PATH_3}:/media3
ports:
- "${PORT:-8096}:8096"
# LAN client auto-discovery. Drop it if you always reach Jellyfin by
# name or through a reverse proxy.
- "${DISCOVERY_PORT:-7359}:7359/udp"
# Jellyfin's own HTTPS listener. Off here: TLS terminates at the proxy.
#- "8920:8920"
# DLNA / SSDP. Only for devices that discover servers that way.
#- "1900:1900/udp"
# ─── Hardware transcoding: Intel Quick Sync / VAAPI ──────────────────
# Delete both keys below if you transcode on the CPU, or replace them
# with the NVIDIA/AMD equivalent. RENDER_GID is host-specific — the
# .env.example explains how to find yours.
devices:
- /dev/dri/renderD128:/dev/dri/renderD128
group_add:
- "${RENDER_GID:?find yours with: getent group render | cut -d: -f3}"
-22
View File
@@ -1,22 +0,0 @@
version: "3.7"
services:
jellyfin:
image: linuxserver/jellyfin
restart: unless-stopped
#hostname: "${DEVICE_HOSTNAME}"
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Paris
volumes:
- ./config:/config
- ../media:/media
# - /media/umbrl/GPass_Serie:/hard_Drive
# - /media/umbrl/Videos:/hdd_movie_all
ports:
# Service auto-discovery
- 7359:7359/udp
- 8096:8096