Files
mediabox-bootstrap/plex/docker-compose.yml
T
claude 8594ef2786 plex: wire the GTX 1070 in beside Quick Sync — NVENC is the only hardware HEVC-out
Mike 2026-08-20, revising the 2026-07-31 iGPU-only call with the 2026-08-04
finding that this UHD 630 has no HEVC encode entrypoint. runtime: nvidia +
NVIDIA_VISIBLE_DEVICES/CAPABILITIES on the plex service; /dev/dri stays.
Also brings the compose current with the live opt-in watchtower config, and
50-plex.sh now sources WATCHTOWER_NOTIFICATION_URL (carries the publish
token) from stacks.env, soft-gates on nvidia-smi, and adds the NVENC line
to the walk-away checks.
2026-08-20 21:13:21 -04:00

169 lines
8.1 KiB
YAML

services:
plex:
# AUTO-UPDATE — Mike's call 2026-08-02, superseding the short-lived pin.
# :latest + the watchtower service below (daily 05:00 check). Remember:
# SQLite schema upgrades are ONE-WAY. The rollback net is Plex's own
# scheduled database backups (Settings > Scheduled Tasks, default every
# 3 days) and the NAS media_pc backup. Watchtower ntfys every update.
image: lscr.io/linuxserver/plex:latest
container_name: plex
restart: unless-stopped
# Watchtower runs in OPT-IN mode (WATCHTOWER_LABEL_ENABLE below), so this
# label is what keeps Plex auto-updating. Remove it and Plex silently
# stops getting updates.
labels:
com.centurylinklabs.watchtower.enable: "true"
# NOT OPTIONAL. Plex's local discovery (GDM) and client auto-detection rely
# on broadcast traffic that a bridge network silently eats. (DLNA is off on
# this server, so discovery — not DLNA — is the reason.) This is also why
# Plex does not sit behind NPM like everything else here: proxy host 17
# already points plex.thewichersfamily.com at 10.0.1.20:32400 and needs no
# change, because the IP does not move.
network_mode: host
environment:
- PUID=3000 # MUST equal uid= on the CIFS mounts
- PGID=3000 # MUST equal gid= on the CIFS mounts
- TZ=${TZ:-America/New_York}
# NVIDIA runtime injection — which GPUs and which driver capabilities
# reach the container. `video` is the capability that carries NVENC.
- NVIDIA_VISIBLE_DEVICES=all
- NVIDIA_DRIVER_CAPABILITIES=compute,video,utility
# VERSION=docker stays even with auto-update: the container must never
# self-update INSIDE (those updates evaporate on recreate). Watchtower
# updates by replacing the image, which is the durable path.
- VERSION=docker
# No PLEX_CLAIM. The migration carries MachineIdentifier,
# ProcessedMachineIdentifier and the online token across in
# Preferences.xml, so this server IS the existing server — already
# claimed, shared users intact, clients not needing to re-add it.
# Claiming would create a NEW server identity and throw that away.
# BOTH hardware paths on purpose — REVISED 2026-08-20 (Mike), updating the
# 2026-07-31 iGPU-only call with what the 2026-08-04 measurement found:
# this UHD 630 has NO HEVC encode entrypoint (H.264 EncSliceLP only — see
# homelab/hosts/mediabox-transcoding.md), so every HEVC-out transcode was
# quietly falling back to software. Quick Sync STAYS for its strengths
# (no session cap, kernel i915 driver, HDR tone mapping); the 1070's NVENC
# is wired in beside it as the only hardware HEVC-out on the box. Plex
# picks per session, and an HEVC target can only be satisfied on NVIDIA.
runtime: nvidia
devices:
# Quick Sync on the UHD 630. Requires the BIOS iGPU Multi-Monitor
# toggle or /dev/dri does not exist.
- /dev/dri:/dev/dri
group_add:
# Render node is root:render and PGID 3000 is not in that group.
# Resolved from the live host by 50-plex.sh — the gid differs across
# distro releases, so do not hardcode it. Verified 993 on this host
# 2026-08-04, which is also the fallback below.
- "${RENDER_GID:-993}"
volumes:
# ---- Plex data directory (SSD): database + Metadata, NOT Media/ -------
# The SQLite database is the most latency-sensitive thing on the box and
# belongs on flash. Metadata/ (25 GB of posters) rides along. The 337 GB
# Media/ preview cache does NOT — see the bind below.
- type: bind
source: /srv/plex/config
target: /config
# ---- Preview/thumbnail cache on the 3 TB ext4 disk — NOT the SSD ------
# Mike's call 2026-07-31: 337 GB is too large for the 500 GB SSD, so
# Media/ lives on /srv/data (the former D:, now ext4) from day one.
# Long-form syntax deliberately: the target path contains spaces, which
# the short "a:b" form handles badly. Docker orders mounts by target
# depth, so this correctly lands inside the /config mount above.
#
# Plex does NOT support relocating this directory natively — this bind
# mount is the mechanism. After first start, force thumbnail generation
# on one title to prove writes succeed across the filesystem boundary
# (EXDEV) — that failure mode can only be tested, not reasoned about.
- type: bind
source: /srv/data/plex-media
target: /config/Library/Application Support/Plex Media Server/Media
# ---- NAS media, mounted at the SAME paths the database stores ---------
# The remap rewrites \\korval\X -> /mnt/nas/X, so the container must see
# exactly /mnt/nas/X. Six separate entries rather than one bind of
# /mnt/nas, because a plain bind does not carry the submounts beneath it.
# Read-only here AND at the mount AND at the Synology — the mediabox NAS
# account has no write permission on the library shares. media_pc (the
# box's write share) is deliberately not exposed to Plex.
#
# These are x-systemd.automount paths. A bind into one TRIGGERS the mount
# (verified 2026-08-04), so a container start after an idle unmount is
# fine. If the NAS itself is unreachable at start, the bind lands on an
# empty directory and the library reads as missing — check the mounts
# first when that happens.
- type: bind
source: /mnt/nas/media
target: /mnt/nas/media
read_only: true
- type: bind
source: /mnt/nas/Radio Shows
target: /mnt/nas/Radio Shows
read_only: true
- type: bind
source: /mnt/nas/Education Videos
target: /mnt/nas/Education Videos
read_only: true
- type: bind
source: /mnt/nas/Health
target: /mnt/nas/Health
read_only: true
- type: bind
source: /mnt/nas/Home Movies
target: /mnt/nas/Home Movies
read_only: true
- type: bind
source: /mnt/nas/Pictures
target: /mnt/nas/Pictures
read_only: true
tmpfs:
# Matches TranscoderTempDirectory=/transcode in the generated
# Preferences.xml. Transcodes are throwaway; keeping them in RAM saves
# a great deal of SSD write wear. 4G of 16G, capped so a pathological
# transcode cannot pressure the rest of the system.
- /transcode:rw,size=4g,mode=1777
watchtower:
# Auto-updater — checks daily at 05:00 local, pulls newer images,
# recreates the container with identical settings, prunes old images,
# and ntfys arrsstack-alerts about anything it did.
#
# OPT-IN MODE (WATCHTOWER_LABEL_ENABLE=true). Watchtower touches ONLY
# containers carrying com.centurylinklabs.watchtower.enable=true.
# Locally-built images (gamelab, shell-mcp) exist in no registry, so under
# the old opt-out mode Watchtower HEAD-requested Docker Hub for them,
# got a 401, and ntfyd a failure every single morning. Opt-in makes the
# safe thing the default: a new local build is ignored unless someone
# deliberately labels it. Anything added here that SHOULD auto-update
# needs enable=true, same as Plex above.
image: containrrr/watchtower
container_name: watchtower
restart: unless-stopped
labels:
com.centurylinklabs.watchtower.enable: "true"
environment:
- TZ=${TZ:-America/New_York}
- WATCHTOWER_LABEL_ENABLE=true
# Watchtower v1.7.1's bundled docker client defaults to API 1.25 and
# its version negotiation fails against Engine 29 (min API 1.40),
# leaving the container in a crash loop. Pinning the API version is
# the documented workaround. 1.44 is safely inside the supported
# window on this host (server 1.55, min 1.40).
- DOCKER_API_VERSION=1.44
- WATCHTOWER_SCHEDULE=0 0 5 * * *
- WATCHTOWER_CLEANUP=true
- WATCHTOWER_NOTIFICATIONS=shoutrrr
- WATCHTOWER_NOTIFICATION_URL=${WATCHTOWER_NOTIFICATION_URL}
- WATCHTOWER_NOTIFICATION_TEMPLATE={{range .}}{{.Message}}{{println}}{{end}}
volumes:
- /var/run/docker.sock:/var/run/docker.sock