Plex¶
Last Updated: 2026-08-11
Two Plex Media Server instances run in the heezy environment — one on the nebula k8s cluster, one on big-boi directly.
big-boi Plex¶
| Property | Value |
|---|---|
| Host | big-boi (192.168.1.21) |
| Container | plex (Docker Compose via Ansible big-boi role) |
| Image | linuxserver/plex |
| Network mode | host |
| Port | 32400 |
| Public URL | plex.trentnielsen.me |
External Access Architecture¶
Internet → 76.112.11.44:443 → FortiGate VIP (plex_bigboi_443)
→ swag-plex LoadBalancer (192.168.1.30:443)
→ swag-plex pod (nginx + Let's Encrypt)
→ bigboi.heezy.local:32400
Port 80 follows the same path via plex_bigboi_80 VIP → 192.168.1.30:80.
FortiGate VIPs (terraform-managed)¶
| Resource | External | Internal | Purpose |
|---|---|---|---|
plex_bigboi_443 |
76.112.11.44:443 | 192.168.1.30:443 | HTTPS via swag-plex |
plex_bigboi_80 |
76.112.11.44:80 | 192.168.1.30:80 | HTTP via swag-plex |
All three plex VIPs and their WAN policies (210, 211, 214) are managed in terraform-heezy/environments/shared/heezy/firewall-objects.tf and firewall.tf.
swag-plex¶
swag-plex is a dedicated SWAG (LinuxServer.io nginx + certbot) instance in the heezy k8s namespace. It handles TLS termination for plex.trentnielsen.me and nebula-plex.trentnielsen.me.
| Property | Value |
|---|---|
| Pod | swag-plex-* in heezy namespace |
| Manifests | heezy-k8s/apps/swag-plex/ |
| MetalLB VIP | 192.168.1.30 |
| Proxy config | swag-plex-proxy-confs ConfigMap |
The nginx proxy config for plex.trentnielsen.me proxies to bigboi.heezy.local:32400. The bigboi.heezy.local DNS entry is served by dnsmasq (192.168.1.29).
ADVERTISE_IP¶
Set in the big-boi docker-compose via OpenBao secret production/heezy/big-boi/plex, key ADVERTISE_IP:
Preferences.xml¶
Persists on the host at /opt/big-boi/plex/Library/Application Support/Plex Media Server/Preferences.xml.
Key settings written directly to this file (Plex must be stopped before editing):
| Setting | Value |
|---|---|
customConnections |
https://plex.trentnielsen.me,http://192.168.1.21:32400 |
LanNetworks |
192.168.1.0/24,192.168.2.0/24 |
customConnections is what gets published to plex.tv for remote client discovery. Without it, plex.tv has no external URL to advertise and remote access fails even if the firewall is correct.
Troubleshooting big-boi Plex¶
Remote access green but mobile app can't see libraries: Check what plex.tv has registered for the server's connections:
docker exec plex curl -s "https://plex.tv/api/resources?includeHttps=1&X-Plex-Token=<token>" | grep -A5 "big-boi"
local="0") connection with the correct external URL. If it shows port 443 pointing to 192.168.1.21, the VIP is misconfigured — it should point to 192.168.1.30 (swag-plex).
Preferences.xml edit procedure:
sudo docker stop plex
# edit /opt/big-boi/plex/Library/Application Support/Plex Media Server/Preferences.xml
sudo docker start plex
nebula k8s Plex¶
| Property | Value |
|---|---|
| Pod | plex-* in heezy namespace |
| Image | lscr.io/linuxserver/plex:latest |
| Manifests | heezy-k8s/apps/plex/ |
| Port | 32400 |
| MetalLB VIP | 192.168.1.28 |
| Network mode | hostNetwork: true |
| Public URL | nebula-plex.trentnielsen.me (via swag-plex) and direct TCP/32400 via plex_nebula_32400 VIP |
External Access Architecture¶
Internet → 76.112.11.44:32400 → FortiGate VIP (plex_nebula_32400)
→ MetalLB VIP 192.168.1.28:32400
→ plex pod
HTTPS via swag-plex also works:
Networking — hostNetwork¶
Plex runs with hostNetwork: true and dnsPolicy: ClusterFirstWithHostNet. This is intentional and critical.
Why: Plex auto-discovers all network interfaces on the host it runs on and registers them with plex.tv as reachable addresses. Without hostNetwork, the pod gets a 10.1.x.x cluster IP. Clients on the LAN receive that address from plex.tv, can't reach it, and fall back to the Plex relay — showing as Remote or Indirect with throttled bandwidth and forced transcoding.
With hostNetwork: true, the pod inherits the node's real LAN IP (192.168.1.x). LAN clients connect directly, showing as Direct with no relay and no forced transcode.
The pod is not pinned to a specific node. It floats freely across nebula-1 through nebula-5. The MetalLB VIP (192.168.1.28) handles routing regardless of which node it lands on.
ADVERTISE_IP¶
customConnections (Preferences.xml)¶
Plex stores custom connection URLs in /config/Library/Application Support/Plex Media Server/Preferences.xml. This file persists on the Longhorn PVC and survives pod restarts.
Current value:
Do not add node IPs here (e.g. 192.168.1.15:32400). Node IPs become stale when the pod reschedules. Always use the MetalLB VIP.
Storage¶
| Volume | Type | Mount | Purpose |
|---|---|---|---|
plex-config |
Longhorn 50Gi | /config |
Plex database, metadata, preferences |
transcode |
emptyDir | /transcode |
Temporary transcode scratch space |
nfs-tv |
NFS | /tv |
TV library |
nfs-movies |
NFS | /movies |
Movies library |
nfs-music |
NFS | /music |
Music library |
dri |
hostPath /dev/dri |
/dev/dri |
Hardware transcoding (Intel QSV) |
Token Restore (init container)¶
An init container (plex-token-restore) runs before Plex starts. It writes PlexOnlineToken into Preferences.xml if it's missing — this handles the case where the config PVC is wiped or restored from backup without the token.
The token is sourced from the plex-secrets k8s secret, key PLEX_TOKEN.
Troubleshooting nebula Plex¶
Clients show Remote or Indirect:
-
Check
The pod IP should behostNetworkis set:192.168.1.x, not10.1.x.x. -
Check
customConnectionsdoesn't contain stale node IPs. -
Restart the pod to re-register with plex.tv:
Pod won't schedule:
With hostNetwork: true, port 32400 must be free on the target node. Check: