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