Skip to content

Jellyfin — playback of the Nextcloud content

Status: deployed. Confirmed by the user on 2026-09-04, together with the fact that Nextcloud content was connected to Jellyfin.

⚠️ The concrete values are TO CONFIRM. This file collects what is confirmed and what can be deduced from the rest of the documentation; every gap is marked. Do not take as true anything carrying TO CONFIRM.

The underlying decision — separating storing from playing

Section titled “The underlying decision — separating storing from playing”
Nextcloud AIO → storage, organisation, sharing, synchronisation
Jellyfin → playback, catalogue, TV and mobile clients
└──────── both over the SAME files, on the 1 TB HDD

This had been floated as an idea since 2026-08-26 and came from a real problem: an HEVC/H.265 4K video at ~60 fps uploaded to Nextcloud gave audio with no picture in the browser. It was not corruption of the upload, it was the codec — the Nextcloud web viewer is not a player. Jellyfin is.

Why not duplicate the files: they are ~129 GB. A second copy for Jellyfin would double the HDD usage and, worse, would create two truths about the same content.

This is what has to be understood before touching anything. The Nextcloud datadir has deliberately restrictive permissions:

/mnt/storage/nextcloud/data UID 33 (www-data) · GID 0 · mode 750
└── <user>/files/... ← the real structure where the files live

Mode 750 means that only UID 33 and GID 0 can read. A Jellyfin container with LinuxServer.io’s default PUID/PGID (1000:1000) cannot read anything there. It has to be resolved, and how it was resolved matters:

Option Consequence
PUID=33 / PGID=0 in Jellyfin Works; Jellyfin runs with Nextcloud’s identity
Adding GID 0 as a supplementary group Works; gives the container membership of the root group
Loosening the datadir to 755 Works, but degrades Nextcloud’s posture — avoid

⚠️ TO CONFIRM: which of the three was used.

volumes:
- /mnt/storage/nextcloud/data:/media:ro # ← the :ro is the important part

Why :ro: Nextcloud keeps its own database of the file tree. Any external write in the datadir puts it out of sync, and recovery means an occ files:scan over 129 GB. Jellyfin has no reason whatsoever to write there — its metadata and cover art go to its own configuration volume.

⚠️ TO CONFIRM: the real mount path and whether the whole datadir was mounted or only one user’s folder.

By the homelab convention, the image should be LinuxServer.io’s:

lscr.io/linuxserver/jellyfin (by convention — TO CONFIRM)
/opt/stacks/jellyfin/ (by the /opt/stacks/<service> convention)

⚠️ TO CONFIRM: the real image and tag, and whether the stack follows the directory convention.

⚠️ TO CONFIRM — and it is the question with the most consequences.

The two options consistent with the network design (see ../infra/networking.md):

A) Published by Caddy jellyfin.balserosworld.net → jellyfin:8096
· follows the pattern of all the other services (NET-06, NET-08)
· needs a block in the Caddyfile + a DNS record
· reachable from TV clients on the LAN by name
B) Tailscale only no block in Caddy, no published port
· more closed, but TV clients do not usually have Tailscale

If (A) was chosen, the site has to be added to ../services/caddy.md and to the Caddyfile table. If Jellyfin publishes ports: on the LAN without going through Caddy, that contradicts NET-08 and it is worth noting it as a conscious exception, just as was done with Nextcloud AIO.

The video that started all this was 4K60 HEVC ~61 Mb/s. The rule adopted from the start: prefer Direct Play, and do not rely on the CPU to transcode without having measured — the Ryzen 3 2200G has 4 threads and already sustains all the rest of the homelab.

The 3 GB GTX 1060 is physically present and unassigned; the initial homelab audit pointed to it precisely for NVENC in Jellyfin. Doing passthrough would involve IOMMU (already enabled) and isolating the GPU from the host.

⚠️ TO CONFIRM: whether passthrough of the GTX 1060 was done, or whether Jellyfin runs only with Direct Play / CPU transcoding.

  • Configuration and metadata: if the stack is in /opt/stacks/jellyfin, it already falls under Layer 2 without touching anything — that path is a source of borg-backup.sh.
  • The media content: it is already covered by Nextcloud AIO’s native backup. Do not add it as a separate source — it would mean backing up the same 129 GB twice.

It is one of the advantages of not duplicating the files: Jellyfin adds practically nothing to the backup volume.

Run on docker-host and dump here whatever comes out:

Terminal window
ls -la /opt/stacks/jellyfin
grep -v -iE 'token|password|key|secret' /opt/stacks/jellyfin/compose.yaml
docker ps --format '{{.Names}}\t{{.Image}}\t{{.Ports}}' | grep -i jelly
grep -A4 -i jelly /opt/stacks/caddy/Caddyfile