Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

Plex Media Server Docker User Guide

Generated April 2026 from plexinc/pms-docker at 1cef65e

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

Plex Media Server in a container, the official way. plexinc/pms-docker is the canonical Docker image published and maintained by Plex, Inc. for running Plex Media Server (PMS) on any Docker-capable host: a Linux server, a NAS (Synology, QNAP, unRAID), a small homelab box, or a Kubernetes cluster via the bundled Helm chart. This guide covers everything a home user, NAS operator, or sysadmin needs to install, configure, and operate a containerized Plex server end-to-end.

1. Quick Start

Plex Media Server organizes your personal video, music, and photo collections and streams them to any Plex-compatible client (TV, phone, web, console, set-top box). The plexinc/pms-docker image is the official containerized build, refreshed on every PMS release for linux/amd64, linux/arm64, and linux/arm/v7 (32-bit ARM with NEON, e.g., Raspberry Pi 3).

Key features:

  • Official multi-arch Docker image, published by Plex, Inc.
  • Three networking modes: host, bridge, macvlan (compose templates included for all three)
  • Auto-update via the beta or public tags, or pin to a specific PMS version for stability
  • Built-in healthcheck that probes http://localhost:32400/identity
  • Intel Quick Sync (/dev/dri) and NVIDIA GPU hardware-transcoding passthrough
  • Auto-detected DVB tuners (/dev/dvb) for Plex Live TV
  • s6-overlay process supervision (graceful shutdown, configurable UID/GID, device-group permission setup)
  • Official Helm chart (charts/plex-media-server) for Kubernetes deployments
  • unRAID community-app template (plex-unRAID.xml) included

Prerequisites:

  • Docker Engine 20.10+ (Linux host strongly preferred)
  • A Plex.tv account
  • Around 10 GB of free disk for /config (large libraries can grow to 100+ GB) plus headroom on /data for your media

Fastest path to a running server (host networking, single command):

docker run -d \
  --name plex \
  --network=host \
  --restart=unless-stopped \
  -e TZ="America/New_York" \
  -e PLEX_CLAIM="claim-xxxxxxxxxxxxxxxxxxxx" \
  -v /opt/plex/config:/config \
  -v /opt/plex/transcode:/transcode \
  -v /mnt/media:/data \
  plexinc/pms-docker

Expected output:

Unable to find image 'plexinc/pms-docker:latest' locally
latest: Pulling from plexinc/pms-docker
... (layers)
Status: Downloaded newer image for plexinc/pms-docker:latest
a1b2c3d4e5f6...

Open http://<host-ip>:32400/web in any browser to finish setup. The server is already claimed under your Plex account because you supplied a PLEX_CLAIM token.

Tip

Get a fresh claim token from plex.tv/claim right before running the command. Tokens expire after roughly four minutes, so generate it last. Once the server is claimed, the variable is ignored on every subsequent start, so you do not have to remove it from your compose file.

What to try next:

  • Pick a permanent install method (Compose, Helm, unRAID) in section 2.
  • Tune environment variables and volume mounts in section 3.
  • Configure hardware transcoding, choose a networking mode, and turn on auto-updates in section 4.
  • Run through the 8 most common day-2 tasks in section 5.

2. Installation

System Requirements

Requirement Minimum Recommended
Docker Engine 20.10+ Latest stable on a Linux host
Host OS Linux, macOS, Windows (with caveats) Linux (Ubuntu, Debian, Fedora, Synology DSM, unRAID)
Architecture amd64, arm64, or arm/v7 (NEON) amd64 for transcoding, arm64 for low-power NAS
RAM 2 GB 4 GB+ for transcoding
/config disk 2 GB 50-200 GB for large libraries (poster art, indexes)
/transcode disk 10 GB SSD or tmpfs for fast transcodes
Network port TCP 32400 Plus UDP discovery ports for full LAN features

Warning

Running this image on Docker Desktop for Windows or macOS is not recommended for production use. Volumes mounted from the host pass through SMB/9PFS, neither of which honor file locking the way SQLite needs. The Plex database will corrupt sooner or later on those volume types. If you must run it there, store /config inside a Docker-managed named volume rather than a host bind mount.

Method 1: docker run

The repo ships three reference Compose templates (bridge, host, macvlan) that document the exact ports and volumes. The same options translate to docker run flags directly.

Host networking

docker run -d \
  --name plex \
  --network=host \
  --restart=unless-stopped \
  -e TZ="America/New_York" \
  -e PLEX_CLAIM="<claimToken>" \
  -v /opt/plex/config:/config \
  -v /opt/plex/transcode:/transcode \
  -v /mnt/media:/data \
  plexinc/pms-docker

Bridge networking

docker run -d \
  --name plex \
  --restart=unless-stopped \
  -p 32400:32400/tcp \
  -p 8324:8324/tcp \
  -p 32469:32469/tcp \
  -p 1900:1900/udp \
  -p 32410:32410/udp \
  -p 32412:32412/udp \
  -p 32413:32413/udp \
  -p 32414:32414/udp \
  -e TZ="America/New_York" \
  -e PLEX_CLAIM="<claimToken>" \
  -e ADVERTISE_IP="http://192.168.1.100:32400/" \
  -h PlexServer \
  -v /opt/plex/config:/config \
  -v /opt/plex/transcode:/transcode \
  -v /mnt/media:/data \
  plexinc/pms-docker

Macvlan networking

docker run -d \
  --name plex \
  --network=physical \
  --ip=192.168.1.50 \
  --restart=unless-stopped \
  -e TZ="America/New_York" \
  -e PLEX_CLAIM="<claimToken>" \
  -h PlexServer \
  -v /opt/plex/config:/config \
  -v /opt/plex/transcode:/transcode \
  -v /mnt/media:/data \
  plexinc/pms-docker

Method 2: Docker Compose

The repo provides three Compose templates: docker-compose-host.yml.template, docker-compose-bridge.yml.template, and docker-compose-macvlan.yml.template. Copy one, fill in the placeholders, and run docker compose up -d.

Host networking

services:
  plex:
    image: plexinc/pms-docker
    container_name: plex
    restart: unless-stopped
    network_mode: host
    environment:
      - TZ=America/New_York
      - PLEX_CLAIM=<claimToken>
    volumes:
      - /opt/plex/config:/config
      - /opt/plex/transcode:/transcode
      - /mnt/media:/data

Bridge networking

services:
  plex:
    image: plexinc/pms-docker
    container_name: plex
    restart: unless-stopped
    hostname: PlexServer
    ports:
      - 32400:32400/tcp
      - 8324:8324/tcp
      - 32469:32469/tcp
      - 1900:1900/udp
      - 32410:32410/udp
      - 32412:32412/udp
      - 32413:32413/udp
      - 32414:32414/udp
    environment:
      - TZ=America/New_York
      - PLEX_CLAIM=<claimToken>
      - ADVERTISE_IP=http://192.168.1.100:32400/
    volumes:
      - /opt/plex/config:/config
      - /opt/plex/transcode:/transcode
      - /mnt/media:/data

Method 3: Kubernetes via the Official Helm Chart

The repository ships a first-class Helm chart at charts/plex-media-server (chart version 1.5.0, app version 1.43.0).

helm repo add plex https://raw.githubusercontent.com/plexinc/pms-docker/gh-pages
helm repo update
helm show values plex/plex-media-server > values.yaml
# edit values.yaml as needed
helm upgrade --install plex plex/plex-media-server \
  --namespace plex --create-namespace \
  --values values.yaml

The chart deploys a single-replica StatefulSet with a PersistentVolumeClaim for /config (default 2Gi, almost certainly too small for any real library — bump it).

Important

Plex Media Server is fundamentally a single-instance application — the SQLite database does not support multiple writers. The chart is intentionally a single-replica StatefulSet. Do not try to scale it horizontally; you will corrupt the database.

Method 4: unRAID

An unRAID community-applications template (plex-unRAID.xml) is included in the repo. Install via the Community Applications plugin, search for Plex Media Server, pick the official plexinc/pms-docker template, and fill in the form. The template defaults to host networking, mounts /config, /transcode, and /data, and exposes PLEX_CLAIM, PLEX_UID (99), PLEX_GID (100), and VERSION as form fields.

Post-Install Verification

Confirm the container is healthy:

docker ps --filter name=plex
CONTAINER ID   IMAGE                STATUS                   PORTS    NAMES
a1b2c3d4e5f6   plexinc/pms-docker   Up 2 minutes (healthy)            plex

Hit the identity endpoint directly:

docker exec plex curl -s http://localhost:32400/identity
<?xml version="1.0" encoding="UTF-8"?>
<MediaContainer size="0" claimed="1" machineIdentifier="..." version="1.43.0..."/>

Note

The image declares HEALTHCHECK --interval=5s --timeout=2s --retries=20 against /healthcheck.sh, which calls curl http://localhost:32400/identity. If docker ps reports (unhealthy), the PMS process inside the container has not started or has crashed — check docker logs plex first.


3. Configuration

Environment Variables

These are the variables the image's init scripts and the underlying PMS binary actually consume. Anything not in this table is not honored.

Variable When applied Default What it does
TZ Every start (unset) Container timezone (e.g., Europe/London, America/Los_Angeles). Used by PMS for scheduled scans and DVR.
PLEX_CLAIM First run only (unset) Claim token from plex.tv/claim. Exchanged once with plex.tv for a permanent server token; ignored afterward.
PLEX_CLAIM_FILE First run only (unset) Path inside the container to a file holding the claim token. Useful with Docker secrets or Kubernetes Secrets.
ADVERTISE_IP First run only (unset) Comma-separated list of full URLs the server should advertise (e.g., http://192.168.1.100:32400/). Required for bridge networking.
ALLOWED_NETWORKS First run only (unset) CIDR list of networks allowed to access PMS without authentication. Example: 192.168.1.0/24,10.0.0.0/8.
PLEX_UID Every start (image default) Numeric UID assigned to the in-container plex user (set via usermod -o -u). Match your host owner.
PLEX_GID Every start (image default) Numeric GID for the plex group (groupmod -o -g).
CHANGE_CONFIG_DIR_OWNERSHIP Every start true If true, runs chown -h plex:plex against /config and /transcode. Set false to skip on big libraries.
HOSTNAME Every start (auto) Container hostname. Most users prefer the -h <name> Docker flag. Not relevant in host networking.
PLEX_UPDATE_CHANNEL Every start (auto) Override the update channel for beta/public tags: 8 = beta, 16 = public.
DEBUG Every start false Set to true for set -x shell tracing in init scripts when troubleshooting startup.

Tip

Run id $(whoami) on the host to find your own UID/GID, then set PLEX_UID and PLEX_GID to match. This is by far the most common fix for "Plex can't see my media" complaints.

Volume Mounts

Container path Required? What lives here
/config Yes The PMS database, preferences, plugins, metadata, posters. Path inside is Library/Application Support/Plex Media Server/. Must be on a filesystem that supports POSIX file locking.
/transcode Recommended Scratch space for the transcoder. Sized for active streams (tens of GB). Map to fast local storage or tmpfs for best results.
/data Yes Where you mount your media. You can mount multiple host directories under /data/movies, /data/tv, /data/music, etc.

Caution

Never put /config on NFS, SMB/CIFS, 9PFS, or a Windows host bind mount. SQLite requires byte-range locking; those filesystems silently mis-implement it and the database will corrupt. The Plex community has been bitten by this for years — assume it applies to you.

Network Ports

Port Protocol Used for
32400 TCP Web UI, API, all client traffic. Required.
8324 TCP Roku Plex Companion control
32469 TCP DLNA server
1900 UDP DLNA / SSDP discovery
32410 UDP GDM (Plex auto-discovery)
32412 UDP GDM
32413 UDP GDM
32414 UDP GDM

Note

Host and macvlan networking expose every port on the host automatically. Bridge networking requires explicit -p/ports mappings — that is why bridge mode also needs ADVERTISE_IP.

For the full configuration reference (Helm values, init-script behavior, build args, internal env vars), see the Plex Media Server Docker Administrator & Developer Guide.


4. Core Usage

Choosing a Networking Mode

Mode Pros Cons Picks for you
host Simplest. Auto-discovery works. Plex sees real client IPs. Remote-access auto-config works. Container shares host's network namespace, so you can't run two Plex servers on one host without changing PMS ports. Most users on a dedicated Plex box
bridge Container is isolated; can run alongside other services that bind 32400 You must manually map every port and set ADVERTISE_IP. Plex sees Docker's gateway IP, not real clients (bandwidth heuristics suffer). Remote access requires manual port forwarding. Multi-tenant Docker hosts where 32400 is already taken
macvlan Container gets its own LAN IP — looks like a separate device on the network Requires a macvlan network created at the Docker level. Host cannot talk to the container directly. Power users; corporate-style segmented LANs

Important

In bridge mode, Plex sees all clients as coming from the Docker gateway (often 172.17.0.1). Configure Settings > Server > Network > LAN Networks with your real LAN CIDR (e.g., 192.168.1.0/24) so PMS applies LAN-bandwidth rules instead of WAN-bandwidth rules.

Image Tags and Auto-Updates

Tag Behavior
latest A specific PMS version, frozen at the time the image was built. To update, pull a new image.
<version> (e.g., 1.43.0.10492-121068a07) Pinned to that exact PMS build. Stable, reproducible, recommended for production.
public No PMS binary baked in. On every container start, cont-init.d/50-plex-update queries https://plex.tv/downloads/details/5 and installs the latest public release.
beta Same as public but pulls the beta channel. Requires Plex Pass. Falls back gracefully when the account isn't subscribed.

Warning

The public and beta tags download and install Plex on every container start. That means slower startup, a network requirement, and a moving target across restarts. For a stable home server, pin to a specific version tag.

Update a fixed-version container:

docker pull plexinc/pms-docker:latest
docker stop plex && docker rm plex
# re-run your original docker run / compose up -d

Update a beta/public container — just restart it:

docker restart plex

Hardware Transcoding (Intel Quick Sync)

If your CPU has an Intel iGPU with Quick Sync Video, pass /dev/dri through. The image's cont-init.d/45-plex-hw-transcode-and-connected-tuner script automatically detects character devices in /dev/dri (and /dev/dvb for tuners), reads each device's GID, creates a matching group inside the container if one doesn't exist, and adds the plex user to it.

docker run -d \
  --name plex \
  --network=host \
  --device=/dev/dri:/dev/dri \
  -e TZ="America/New_York" \
  -e PLEX_CLAIM="<claimToken>" \
  -v /opt/plex/config:/config \
  -v /opt/plex/transcode:/transcode \
  -v /mnt/media:/data \
  plexinc/pms-docker

Then in the Plex web app: Settings > Server > Transcoder > Show Advanced > Use hardware acceleration when available (and Use hardware-accelerated video encoding).

Note

Hardware transcoding is a Plex Pass feature. Without a Plex Pass subscription, those toggles are disabled and the server falls back to software transcoding regardless of GPU passthrough.

Hardware Transcoding (NVIDIA)

For NVIDIA GPU transcoding, install the NVIDIA Container Toolkit on the host and pass GPUs in:

docker run -d \
  --name plex \
  --network=host \
  --gpus all \
  -e TZ="America/New_York" \
  -e PLEX_CLAIM="<claimToken>" \
  -e NVIDIA_VISIBLE_DEVICES=all \
  -e NVIDIA_DRIVER_CAPABILITIES=compute,video,utility \
  -v /opt/plex/config:/config \
  -v /opt/plex/transcode:/transcode \
  -v /mnt/media:/data \
  plexinc/pms-docker

The Helm chart automates all of this when pms.gpu.nvidia.enabled=true.

Headless Server Setup (No Claim Token)

If you start the container without PLEX_CLAIM, the first-run setup wizard refuses to load remotely (Plex insists you connect from localhost). Tunnel through SSH:

ssh user@plex-host -L 32400:plex-host:32400 -N

Then open http://localhost:32400/web on your laptop.

Container Management Cheatsheet

Action Command
Start docker start plex
Stop docker stop plex
Restart (also re-runs auto-update on beta/public) docker restart plex
Shell in docker exec -it plex /bin/bash
Live logs docker logs -f plex
Healthcheck docker exec plex /healthcheck.sh && echo OK
Fine-grain service control docker exec plex /plex_service.sh -r (-u up, -d down, -r restart)

5. Common Tasks

Recipe 1: First-Time Plex Pass Claim Flow

Goal: Bring up a brand-new server, claim it under your Plex account, and complete the setup wizard.

Prerequisites: A Plex.tv account (Plex Pass optional).

  1. Visit plex.tv/claim while logged in. Copy the claim-... token.

  2. Within four minutes, run the Quick Start command from section 1 (or paste the token into your compose file and docker compose up -d).

  3. Watch the logs for the claim handshake:

    docker logs -f plex 2>&1 | grep -i claim
    
    Attempting to obtain server token from claim token
    Token obtained successfully
    
  4. Open http://<host-ip>:32400/web. The web UI should already show Welcome to Plex — finish the wizard (server name, libraries, remote access).

  5. Verify: docker logs plex ends with Plex Media Server first run setup complete.

If it fails: A Failed to obtain server token line means the claim expired before exchange — re-grab it and restart.

Recipe 2: Mount a NAS Media Share

Goal: Stream media that lives on a separate NAS over NFS.

  1. Mount the NAS share on the Docker host (NOT inside the container — the container should see it as a local bind mount):

    sudo mkdir -p /mnt/nas-media
    echo 'nas.lan:/volume1/media /mnt/nas-media nfs ro,vers=4,_netdev 0 0' \
      | sudo tee -a /etc/fstab
    sudo mount /mnt/nas-media
    
  2. Bind-mount it as /data in your compose file:

    volumes:
      - /opt/plex/config:/config
      - /opt/plex/transcode:/transcode
      - /mnt/nas-media:/data:ro
    
  3. Restart Plex; add libraries pointed at /data/movies, /data/tv, etc.

Caution

Never mount the NAS as /config. NFS does not support SQLite locking reliably and will corrupt the Plex database. NAS for media (/data) only — keep /config on local disk.

Recipe 3: Docker Compose with Watchtower Auto-Updates

Goal: Have a separate container watch for new plexinc/pms-docker images and rebuild Plex automatically.

services:
  plex:
    image: plexinc/pms-docker:latest
    container_name: plex
    restart: unless-stopped
    network_mode: host
    environment:
      - TZ=America/New_York
    volumes:
      - /opt/plex/config:/config
      - /opt/plex/transcode:/transcode
      - /mnt/media:/data
    labels:
      - "com.centurylinklabs.watchtower.enable=true"

  watchtower:
    image: containrrr/watchtower
    container_name: watchtower
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - WATCHTOWER_CLEANUP=true
      - WATCHTOWER_LABEL_ENABLE=true
      - WATCHTOWER_SCHEDULE=0 0 4 * * *  # 4am daily

Tip

Combine Watchtower with image: plexinc/pms-docker:latest rather than the public/beta tags. Watchtower handles image churn at the registry level; the dynamic tags handle it at startup. Mixing both creates redundant downloads.

Recipe 4: GPU Passthrough (Intel iGPU and NVIDIA)

Goal: Move transcoding off the CPU.

Intel Quick Sync

services:
  plex:
    image: plexinc/pms-docker
    network_mode: host
    devices:
      - /dev/dri:/dev/dri
    environment:
      - TZ=America/New_York
    volumes:
      - /opt/plex/config:/config
      - /opt/plex/transcode:/transcode
      - /mnt/media:/data

NVIDIA

services:
  plex:
    image: plexinc/pms-docker
    network_mode: host
    runtime: nvidia
    deploy:
      resources:
        reservations:
          devices:
            - capabilities: [gpu]
    environment:
      - TZ=America/New_York
      - NVIDIA_VISIBLE_DEVICES=all
      - NVIDIA_DRIVER_CAPABILITIES=compute,video,utility
    volumes:
      - /opt/plex/config:/config
      - /opt/plex/transcode:/transcode
      - /mnt/media:/data

After enabling, toggle Use hardware acceleration when available and Use hardware-accelerated video encoding in Settings > Server > Transcoder > Show Advanced. The container's cont-init.d/45-… script handles the device-group plumbing automatically.

Recipe 5: Back Up the Plex Database

Goal: Take a clean, restorable snapshot of /config.

  1. Stop the container so SQLite is quiescent:

    docker stop plex
    
  2. Tar /config (preserving permissions):

    tar -czpf plex-config-$(date +%F).tar.gz \
      --exclude='Library/Application Support/Plex Media Server/Cache' \
      --exclude='Library/Application Support/Plex Media Server/Crash Reports' \
      -C /opt/plex/config .
    
  3. Restart:

    docker start plex
    

Caution

Do not copy /config while the container is running. SQLite WAL files can be in an inconsistent state; the resulting backup may load but show silent corruption days later when an unrelated table is touched.

Recipe 6: Migrate from a Native Install to Docker

Goal: Move an existing bare-metal Plex installation into the container without losing the library, watch state, or metadata.

  1. On the native host, locate the Plex data directory:

    • Linux package: /var/lib/plexmediaserver/Library/Application Support/
    • macOS: ~/Library/Application Support/Plex Media Server
    • Windows: %LOCALAPPDATA%\Plex Media Server
  2. Stop the native Plex service so the database is quiet (sudo systemctl stop plexmediaserver, or quit the macOS menu-bar app, or stop the Windows service).

  3. Recreate the expected layout under your container /config host path:

    sudo mkdir -p '/opt/plex/config/Library/Application Support'
    sudo cp -a '/var/lib/plexmediaserver/Library/Application Support/Plex Media Server' \
      '/opt/plex/config/Library/Application Support/'
    sudo chown -R 1000:1000 /opt/plex/config
    
  4. Bring up the container with that path mounted as /config — and without a PLEX_CLAIM (the database is already claimed):

    docker compose up -d
    
  5. Wait for the healthcheck, then verify your libraries appear in the web UI exactly as before. Disable or uninstall the native service only after you confirm the container is happy.

Important

The container's plex user defaults to UID/GID 1000. Adjust the chown command if your native install used a different owner — or set PLEX_UID/PLEX_GID in compose to match the existing files.

Recipe 7: Pin to a Specific Version for Stability

Goal: Stop being on the moving latest tag and lock to a known-good PMS build.

  1. Pick a known-good version tag from the Docker Hub tags page (e.g., 1.43.0.10492-121068a07).

  2. Update your compose file:

    services:
      plex:
        image: plexinc/pms-docker:1.43.0.10492-121068a07
    
  3. Recreate the container:

    docker compose up -d
    
  4. Verify with docker exec plex curl -s http://localhost:32400/identity — the version= attribute should match the pinned tag.

Tip

Pinned tags are also the right choice for offline or air-gapped installs. Both public and beta reach out to plex.tv on every start; pinned tags don't.

Recipe 8: Run on Synology DSM or unRAID

Goal: Use the official image on a NAS appliance.

Synology DSM (Container Manager / Portainer)

  1. Container Manager > Registry, search plexinc/pms-docker, download latest (or a pinned tag).
  2. Create a container, use bridge networking, map ports 32400/tcp, 1900/udp, 32400-32414/udp.
  3. Mount three host folders to /config, /transcode, /data.
  4. Add environment variables TZ, PLEX_CLAIM, ADVERTISE_IP=http://<dsm-ip>:32400/.
  5. Run; complete setup at http://<dsm-ip>:32400/web.

unRAID

  1. Apps > Community Applications > search "Plex Media Server" and pick the official plexinc/pms-docker template (the repo ships plex-unRAID.xml for this).
  2. The template defaults to host networking and pre-configures /config, /transcode, /data, PLEX_CLAIM, PLEX_UID=99, PLEX_GID=100, and VERSION.
  3. Set PLEX_CLAIM, point /data at your /mnt/user/media share, and click Apply.

Note

Synology's default Container Manager UI does not support --network=host — you have to use bridge mode and set ADVERTISE_IP. unRAID supports host networking out of the box, which is why the bundled template uses it.


6. Troubleshooting

Diagnostic Decision Tree

Container won't start
  ├─ docker logs plex
  │    ├─ "Could not get install version"
  │    │   └─ network blocked, or invalid TAG. Pin a version.
  │    ├─ "Failed to fetch update"
  │    │   └─ download truncated or DNS failure
  │    └─ "s6-supervise (child): fatal: unable to exec run: Permission denied"
  │        └─ RHEL/Fedora docker patch — add  -v /run
  │
Container starts but (unhealthy)
  ├─ docker exec plex curl http://localhost:32400/identity
  │    ├─ "Connection refused" → PMS process didn't start
  │    └─ XML returned       → healthcheck transient, wait 1 min
  │
Healthy but clients can't see it
  ├─ Bridge networking?     → set ADVERTISE_IP, forward 32400
  ├─ Host networking?       → host firewall (ufw/firewalld)
  └─ Macvlan?               → host can't talk to container by design

Error: s6-supervise (child): fatal: unable to exec run: Permission denied

Cause: Fedora, CentOS Stream, RHEL, and AlmaLinux ship a Docker build that mounts /run non-executable. s6-overlay puts its service-supervision sockets there.

Solution: Add a tmpfs /run:

docker run ... -v /run plexinc/pms-docker

Or in compose:

volumes:
  - /run

Prevention: Always include -v /run on RHEL-family hosts.

Error: Could not get install version

Cause: Container is on the beta or public tag and cannot reach plex.tv to look up the latest build, or the URL parameters generated from version.txt (architecture / channel / distro) don't resolve.

Solution:

  1. Check connectivity from inside: docker exec plex curl -I https://plex.tv
  2. If using beta, confirm the linked Plex account has Plex Pass.
  3. Switch to a pinned version tag for offline-resilient startup.

Prevention: Use a pinned tag for production.

Error: Failed to fetch update

Cause: The downloaded .deb from plex.tv was either smaller than 10 KB or the curl exit code was non-zero. The image's installFromRawUrl() function checks both.

Solution:

  1. Restart: docker restart plex (transient network issues recover).
  2. Persistent? Build locally with --build-arg URL=https://... and a known-good .deb URL.

Prevention: Pin to a baked-in version tag.

Error: Database corruption (slow scans, crashes after weeks)

Cause: /config is on NFS, SMB, 9PFS, or a Docker Desktop bind mount — none of which expose POSIX locking the way SQLite expects.

Solution:

  1. Stop the container.
  2. Move /config to local block storage (ext4, XFS, ZFS, or a Docker named volume on the same).
  3. Restart.

Prevention: The single hardest "must-do" rule with this image — /config on local block storage only.

Error: Cannot reach Plex from outside the LAN (bridge mode)

Cause: Plex is double-NAT'd (Docker bridge + your home router) and can't auto-configure port forwarding.

Solution:

  1. Set ADVERTISE_IP=http://<host-public-or-LAN-ip>:32400/ in the container env.
  2. Forward TCP 32400 on your router to the Docker host.
  3. Settings > Server > Remote Access in the web UI — manually set the external port if it differs.

Prevention: Use host networking (no double NAT).

Error: Plex can't read media — Permission denied in scan logs

Cause: The in-container plex user's UID/GID does not match the ownership of files in your bind-mounted /data.

Solution:

  1. On the host: id $(whoami) — note uid=1000(jane) gid=1000(jane).
  2. Set PLEX_UID=1000, PLEX_GID=1000 in the env block.
  3. Restart the container — the 40-plex-first-run script re-runs usermod/groupmod.

Prevention: Always set PLEX_UID/PLEX_GID to match your media-owning host user.

Error: Setup wizard refuses to load on a remote browser

Cause: Plex enforces a localhost-only setup wizard until the server is claimed. With no PLEX_CLAIM, the only way to claim is from a browser whose origin is localhost.

Solution:

  1. Add PLEX_CLAIM to the compose env block and recreate; or

  2. SSH-tunnel to the server:

    ssh user@server -L 32400:server:32400 -N
    

    Then open http://localhost:32400/web on your laptop.

Prevention: Always include PLEX_CLAIM on first run.


7. FAQ

Q: Where do I get a claim token?

plex.tv/claim while signed into your Plex account. The token is roughly four minutes long, single-use, and is exchanged for a permanent server token by cont-init.d/40-plex-first-run on the next container start.

Q: Which networking mode should I pick?

If nothing else is competing for port 32400, use host. Bridge adds friction (port mapping, ADVERTISE_IP, double-NAT remote-access problems) for very little gain on a single-purpose Plex box.

Q: What architectures are supported?

The official image is published for linux/amd64, linux/arm64, and linux/arm/v7 (32-bit ARM with NEON, e.g., Raspberry Pi 3/4 in 32-bit mode). The container's installBinary.sh selects linux-x86_64, linux-aarch64, or linux-armv7hf_neon from the Plex download API based on ${TARGETARCH}/${TARGETPLATFORM}.

Q: How do updates work?

  • Pinned tag (e.g., 1.43.0.10492-121068a07): static; pull a new image to update.
  • latest: static at build time; pull when a new build lands.
  • public / beta: dynamic; the container runs cont-init.d/50-plex-update on every start, queries https://plex.tv/downloads/details/5, and installs the latest .deb.

Q: Do I need a Plex Pass?

No for basic streaming and library management. Yes for hardware transcoding, mobile sync, DVR/Live TV, and the beta channel.

Q: Can I run multiple media directories?

Yes — repeat -v flags or list multiple lines in volumes::

volumes:
  - /mnt/movies:/data/movies
  - /mnt/tv:/data/tv
  - /mnt/music:/data/music

Then in the web UI, point each library at its own subdirectory under /data.

Q: What does CHANGE_CONFIG_DIR_OWNERSHIP do?

By default the container runs chown -h plex:plex against /config and /transcode on every start. For a 100 GB+ library this can add a few minutes of startup time. If you've already set ownership correctly on the host, set CHANGE_CONFIG_DIR_OWNERSHIP=false.

Q: How do I back up my library?

Stop the container, tar /config (skip Cache/ and Crash Reports/), restart. See recipe 5.

Q: Does this image auto-update?

Only on the public and beta tags. Standard tags freeze the binary at build time; you must pull a new image. Use Watchtower if you want hands-off updates with a pinned-tag style.

Q: Can I run this on Kubernetes?

Yes. Use the bundled Helm chart at charts/plex-media-server. It deploys a single-replica StatefulSet, optionally with an nginx Ingress, NVIDIA GPU support, and an rclone sidecar for cloud-mounted media. See the Admin Guide for the full values reference.

Q: Where do the init scripts live, in case I want to read them?

Inside the container, at /etc/cont-init.d/40-plex-first-run, /etc/cont-init.d/45-plex-hw-transcode-and-connected-tuner, and /etc/cont-init.d/50-plex-update. They are part of the s6-overlay init lifecycle. The shared shell library is /plex-common.sh.

Q: How do I file a bug?

Open an issue at github.com/plexinc/pms-docker. Include the output of docker version, docker logs plex, your docker run/compose, and (if relevant) the host distro and kernel.