This commit is contained in:
2026-09-13 17:00:56 +02:00
parent 1709657472
commit 24db4cfbb8
6 changed files with 276 additions and 10 deletions

122
docs/plex.md Normal file
View File

@@ -0,0 +1,122 @@
# Plex
**Purpose:** Self-hosted media server for streaming TV and movies from the local library.
| Property | Value |
| ------------ | ----------------------------------------------------------------------------- |
| Image | `lscr.io/linuxserver/plex:latest` |
| Web UI | <https://plex.home.jens.pub> |
| Compose file | [`../services/plex/docker-compose.yml`](../services/plex/docker-compose.yml) |
Current upstream version: **1.43.4.10903** (`1.43.4.10903-e5521bd8c-ls324`). The `latest` tag is used
deliberately — LinuxServer recommends it for Plex, and `VERSION=docker` lets the image track the
current public build. Plex also self-updates inside the container.
---
## Environment Variables
| Variable | Description |
| ------------- | ---------------------------------------- |
| `PUID` | Host user ID for volume ownership |
| `PGID` | Host group ID for volume ownership |
| `TZ` | Timezone (e.g. `Europe/Berlin`) |
| `TV_PATH` | Host path to TV library |
| `MOVIES_PATH` | Host path to movies library |
| `PLEX_CLAIM` | Claim token for first-time setup |
`VERSION=docker` is set in the compose file (not Portainer) and means "let the image decide the Plex
version".
---
## First-time setup (claiming)
Plex refuses the setup wizard when the request arrives through a reverse proxy with an unknown
`Host` header (it treats it as non-local), so the web UI shows *"Not authorized — You do not have
access to this server"* until the server is claimed. Claim it with a **claim token**:
1. Open <https://plex.tv/claim> while logged in and copy the token (it expires after ~4 minutes).
2. Put it in the `PLEX_CLAIM` value of the `plex` stack in Portainer.
3. Restart the stack: `knecht restart plex` (or `knecht update plex`).
4. Open <https://plex.home.jens.pub/web> — the server is now claimed to your account.
`PLEX_CLAIM` can be emptied again afterwards; it is only read while the server is unclaimed.
### After claiming
Plex clients (Apple TV, mobile, TV apps) do **not** use the Traefik domain. They read the server's
connection list from plex.tv and connect directly, so two things are needed:
1. **Publish the client ports** — see [Ports](#ports) below. Otherwise the only advertised address is
the container IP (`172.18.0.11`), which no client can reach.
2. **Custom server access URLs** = `http://192.168.178.193:32400` (host LAN IP) and optionally
`https://plex.home.jens.pub`. Set via **Settings → Network** or the API:
```sh
curl -X PUT "http://192.168.178.193:32400/:/prefs\
?customConnections=http://192.168.178.193:32400,https://plex.home.jens.pub\
&allowedNetworks=192.168.178.0/24,172.18.0.0/16\
&X-Plex-Token=<PlexOnlineToken>"
```
The token lives in `/config/Library/Application Support/Plex Media Server/Preferences.xml`
(`PlexOnlineToken`).
`allowedNetworks` (LAN Networks) makes the LAN and the Docker subnet count as local, avoiding the
remote bandwidth cap. After this, plex.tv advertises e.g. `https://192-168-178-193.<hash>.plex.direct:32400`
(`local=1`) and clients connect directly.
---
## Ports
Bridge networking means Plex clients can only reach the server if its ports are published on the
host. GDM multicast discovery does **not** traverse the Docker bridge — clients rely on the plex.tv
connection list instead.
| Port | Purpose |
| --------------- | ------------------------- |
| `32400/tcp` | Web UI + client streaming |
| `8324/tcp` | Plex Companion |
| `32469/tcp` | DLNA |
| `32410-32414/udp` | GDM local discovery |
`1900/udp` (SSDP) and `5353/udp` (mDNS) are deliberately **not** published — they clash with the
host's own Avahi/SSDP services and aren't needed for connectivity.
If clients still misbehave (e.g. rely on GDM discovery), `network_mode: host` is LinuxServer's
recommended mode and makes discovery work — at the cost of the Traefik route for the web UI.
---
## Volumes
| Volume | Mount | Purpose |
| -------------- | ------------------ | ---------------------------- |
| `plex_config` | `/config` | Database, metadata, settings |
| `${TV_PATH}` | `/media/tv:ro` | TV library (read-only) |
| `${MOVIES_PATH}`| `/media/movies:ro`| Movies library (read-only) |
| tmpfs | `/transcode` (4 GB)| RAM transcoding scratch |
Media is mounted read-only — Plex only reads, Sonarr/Radarr manage the files.
The transcode directory is a **tmpfs** rather than a named volume: LinuxServer removed `/transcode`
volume support, and transcoding to RAM is faster and avoids SSD wear. The 4 GB is a cap, not a
reservation.
---
## Networks
- `proxy` (external) — Traefik routes `plex.home.jens.pub` → port `32400`
- Host ports `32400`, `8324`, `32469`, `32410-32414/udp` published directly (see [Ports](#ports))
Plex is intentionally **not** routed through the VPN. Like Jellyfin it serves local clients and
doesn't need outbound anonymity.
### Remote access
Because `32400` is published on the host, Plex's remote-access check passes and it is reachable
from outside the LAN (router port-forward / UPnP required). The public address is reported by Plex
as the WAN IP.