initial commit
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
---
|
||||
name: plex-setup
|
||||
description: Deploy and configure Plex Media Server on a self-hosted Linux server — Docker, GPU hardware transcoding, network configuration, media library management, and troubleshooting.
|
||||
version: 1.1.0
|
||||
author: Hermes Agent
|
||||
license: MIT
|
||||
platforms: [linux]
|
||||
metadata:
|
||||
hermes:
|
||||
tags: [plex, media-server, docker, nvidia, transcoding, self-hosted]
|
||||
category: self-hosting
|
||||
related_skills: [docker-gpu-acceleration, samba-nas]
|
||||
---
|
||||
|
||||
# Plex Media Server Setup
|
||||
|
||||
Deploy and manage Plex Media Server via Docker on a self-hosted Linux server. Covers first-run setup, GPU hardware transcoding, network configuration, media library management, sonic analysis, Plex API interaction, and troubleshooting.
|
||||
|
||||
## When to Use
|
||||
|
||||
- User asks to set up Plex on their homelab/server
|
||||
- User wants hardware-accelerated transcoding (NVIDIA, Intel QuickSync)
|
||||
- User wants to add media drives to an existing Plex container
|
||||
- User asks about sonic analysis, Plexamp DJ, or "sonically similar" features
|
||||
- User needs to query or manage Plex programmatically via API
|
||||
- User is migrating Plex to a new server
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker + docker-compose-v2 installed
|
||||
- NVIDIA driver installed (for NVIDIA GPU transcoding) OR Intel GPU for QuickSync
|
||||
- nvidia-container-toolkit configured (for NVIDIA GPUs)
|
||||
- Plex account and an active Plex Pass (for hardware transcoding)
|
||||
|
||||
## Setup Steps
|
||||
|
||||
### 1. Create directory structure
|
||||
|
||||
```bash
|
||||
mkdir -p ~/docker/plex/config
|
||||
```
|
||||
|
||||
- `config/` — Persistent Plex data (database, metadata, preferences)
|
||||
|
||||
### 2. Create docker-compose.yml
|
||||
|
||||
```yaml
|
||||
services:
|
||||
plex:
|
||||
image: plexinc/pms-docker:latest
|
||||
container_name: plex
|
||||
network_mode: host
|
||||
environment:
|
||||
- TZ=America/New_York
|
||||
- PLEX_CLAIM=claim-<your-claim-token>
|
||||
- ADVERTISE_IP=http://<server-lan-ip>:32400/
|
||||
volumes:
|
||||
- /home/ray/docker/plex/config:/config
|
||||
- /dev/shm:/transcode
|
||||
devices:
|
||||
- /dev/dri:/dev/dri
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
**Key configuration details:**
|
||||
|
||||
- **`network_mode: host`** — Required for DLNA/GDM discovery, remote access, and automatic local network detection. Without host mode, Plex can't broadcast its presence on the LAN.
|
||||
- **`PLEX_CLAIM`** — One-time token from https://plex.tv/claim. Expires ~5 minutes after generation. Set it, start the container once, then remove it (optional but recommended after first run).
|
||||
- **`ADVERTISE_IP`** — Must be the server's actual LAN IP (check with `ip -4 addr show | grep -oP '(?<=inet\s)\d+\.\d+\.\d+\.\d+' | grep -v 127.0.0.1`). Required for proper remote access configuration. DO NOT guess this — verify the IP first.
|
||||
- **`/dev/dri`** — Device passthrough for hardware transcoding. Works for both NVIDIA and Intel GPUs. NVIDIA also needs the `nvidia` Docker runtime in some configurations, but `/dev/dri` alone covers VA-API/NVDEC for Plex.
|
||||
- **`/dev/shm:/transcode`** — RAM-backed transcode directory. Prevents SSD wear from heavy transcoding and improves performance. Ensure the server has enough RAM (minimum 2GB free for 4K transcodes).
|
||||
|
||||
### 3. Get a claim token and start
|
||||
|
||||
1. Visit https://plex.tv/claim and copy the token (expires in ~5 minutes)
|
||||
2. Add `PLEX_CLAIM=claim-<token>` to the compose file
|
||||
3. Start the container:
|
||||
```bash
|
||||
cd ~/docker/plex && docker compose up -d
|
||||
```
|
||||
4. Verify it's healthy:
|
||||
```bash
|
||||
docker ps --filter name=plex
|
||||
docker logs plex | tail -10
|
||||
```
|
||||
5. Look for "Token obtained successfully" in logs — confirms the server is linked to your Plex account
|
||||
|
||||
### 4. Access and configure
|
||||
|
||||
Open `http://<server-ip>:32400/web` in a browser. The server should appear as claimed and ready. If it doesn't automatically detect:
|
||||
- Check `ADVERTISE_IP` is correct
|
||||
- Restart the container with updated config
|
||||
|
||||
### 5. Add media libraries (after first run)
|
||||
|
||||
Once the server is running and claimed, add media mounts as volumes. **Do this after the first run** — get the server healthy and accessible first, then add media:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- /home/ray/docker/plex/config:/config
|
||||
- /dev/shm:/transcode
|
||||
- /mnt/media/Videos:/data/media/Movies:ro
|
||||
- /mnt/media/Music:/data/media/Music:ro
|
||||
- /mnt/wd-passport/Videos:/data/media/WDVideos:ro
|
||||
```
|
||||
|
||||
**Pitfall:** Binding mount paths into `/data/media/` is convention, not requirement. Plex lets you point at any path inside the container. Use whatever layout makes sense, then add libraries via the Plex web UI pointing at the container paths.
|
||||
|
||||
Recreate the container to apply the new volume mounts (restart won't pick them up):
|
||||
```bash
|
||||
docker compose up -d plex
|
||||
```
|
||||
|
||||
**Why:** `docker compose restart` reuses the existing container config — new volume mounts won't appear. `up -d` recreates with the updated compose file, which is required for volume, device, or environment changes.
|
||||
|
||||
### 6. Verify hardware transcoding
|
||||
|
||||
1. Play a media file that requires transcoding (high-bitrate 4K, or audio format the client doesn't support)
|
||||
2. Check Plex Dashboard (web UI top-right activity icon) or:
|
||||
```bash
|
||||
# In the Plex logs
|
||||
docker logs plex 2>&1 | grep -i "hwaccel\|transcode\|nvidia\|nvdec"
|
||||
```
|
||||
3. On a successful HW transcode, you should see lines indicating NVDEC/NVENC or VA-API usage
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- **`PLEX_CLAIM` expired** — The token is only valid for ~5 minutes. If you didn't start the container in time, remove `PLEX_CLAIM` from the compose, restart, and the server remains unclaimed. To re-claim: generate a new token, re-add it, run `docker compose up -d` (NOT restart — `restart` doesn't re-read env vars, only `up -d` does), then remove the token after success.
|
||||
- **`ADVERTISE_IP` wrong** — If you guess the IP, remote access and local discovery will fail. Always check `ip -4 addr show` and set it to the correct LAN IP before first start.
|
||||
- **`libusb_init failed` in logs** — Harmless. Means no USB TV tuner is attached. Ignore it.
|
||||
- **`chown: missing operand after 'plex:plex'` in logs** — Cosmetic during first init. Harmless.
|
||||
- **Hardware transcoding not working** — Check that `/dev/dri` is accessible inside the container: `docker exec plex ls -la /dev/dri`. If the device doesn't exist, verify NVIDIA driver is installed and the container was created with `--device /dev/dri`. For NVIDIA specifically, you may also need `runtime: nvidia` in the compose or `--gpus all` on `docker run`.
|
||||
- **Plex not showing on local network** — Host network mode usually fixes this. If still not visible, check firewall rules (ufw/iptables blocking UDP 1900 for SSDP).
|
||||
- **Media mount paths** — Use `:ro` (read-only) for all media mounts. Plex only reads from these directories; write access is unnecessary and risks accidental file modification.
|
||||
- **`docker compose restart` vs `up -d`** — `compose restart` reuses the existing container config and only restarts the process. `compose up -d` recreates the container with any config changes. Use `up -d` when you change `environment`, `devices`, or `volumes`; use `restart` for a simple process restart.
|
||||
- **Plexamp DJ blocked after sonic analysis** — Even with completed analysis, Plexamp DJ features may still show *"Requires Sonic Analysis"*. Top causes: (1) `MusicAnalysisBehavior` server pref is `manual` instead of `scheduled` — Plexamp reads this server-level flag and blocks DJ when it's not `scheduled`. Fix: `PUT /:/prefs?MusicAnalysisBehavior=scheduled`. (2) Plexamp cached old server state — force-quit and reopen. (3) Sonic analysis indexes need reload — restart the Plex container. See `references/plex-management.md` for full troubleshooting.
|
||||
- **Plex API token mangled by bash shell** — Plex tokens like `fjCWcQF_trzJHgYKhRXV` contain underscores and other chars that bash may mangle in shell string expansion. If `curl -s "http://...?X-Plex-Token=$TOKEN"` returns 401 but the token is correct, the shell is the culprit. **Use Python urllib instead**: read the token with `xml.etree.ElementTree`, build the URL with `urllib.request.Request`, and the token passes through unmolested. The `curl` command works fine when the token is copied literally — the problem is bash variable interpolation, not the API itself.
|
||||
- **Compilation folders showing as "Various Artists"** — When Plex scans a parent folder containing multiple compilation subfolders, it can cross-contaminate album grouping even with `respectTags=true`. **Fix: add each subfolder as an individual library location instead of the parent.** Process: (1) Insert rows into `section_locations` table in the Plex SQLite DB (`com.plexapp.plugins.library.db`) with `library_section_id` and `root_path` for each subfolder. (2) Delete the old parent location row. (3) Restart Plex container and trigger a library refresh. This isolates each compilation folder so they appear as distinct entities in Plex. Use Python + sqlite3 module (not the `sqlite3` CLI, which may not be installed).
|
||||
- **`respectTags` change has no effect on existing items** — Changing `respectTags` from false→true (or vice versa) does NOT cause Plex to re-read embedded tags on already-matched albums. "Refresh Metadata" in the web UI and `GET /refresh?force=1` will appear to do nothing. You MUST either touch all files to update their mtime (so Plex sees them as "changed") then run a forced scan, or perform the Plex Dance (remove location→scan→empty trash→re-add location→scan) to reprocess from scratch. See `references/plex-management.md` for both workflows.
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
- [ ] Container is `Up` and `healthy` in `docker ps`
|
||||
- [ ] `http://<server-ip>:32400/web` loads the Plex web interface
|
||||
- [ ] Server appears as claimed (linked to Plex account)
|
||||
- [ ] `/dev/dri` is accessible inside the container
|
||||
- [ ] Media libraries are scannable after adding mounts
|
||||
- [ ] Hardware transcoding is active (check Plex Dashboard during a transcode)
|
||||
- [ ] Remote access is configured (if desired)
|
||||
|
||||
## Managing Plex After Setup
|
||||
|
||||
For programmatic management — API endpoints, sonic analysis, database queries, butler task scheduling, tag-based artist/album grouping, file permission troubleshooting (DOSATTRIB xattrs), and the \"Various Artists\" fix — see **`references/plex-management.md`**. For Plex 1.43.x `--analyze-loudness` regression / DJ Friendgänger blockage, see **`references/plex-1.43-loudness-debug.md`** (full investigation + diagnostic workflow).\n\nBundled scripts:\n- `scripts/fix-compilation-tags.py` — add `compilation=1` + `albumartist=Various Artists` to untagged tracks (for singles/compilations that should appear as standalone entries)\n- `scripts/strip-various-artists.py` — REMOVE `album_artist=Various Artists` and empty `album_artist` from files so Plex uses the track-level `artist` tag (for tracks wrongly grouped under \"Various Artists\")
|
||||
|
||||
Covers:
|
||||
- Plex HTTP API (sections, preferences, activities, tokens)
|
||||
- Sonic analysis: enabling, triggering immediately, monitoring progress
|
||||
- Butler scheduling and forced task execution
|
||||
- Database inspection (fingerprint state, activity history)
|
||||
- Common pitfalls (fpcalc myths, butler timing, progress interpretation)
|
||||
Reference in New Issue
Block a user