Plex Media Server in a container, the official way.
plexinc/pms-dockeris 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
betaorpublictags, 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/datafor 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
/configinside 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=20against/healthcheck.sh, which callscurl http://localhost:32400/identity. Ifdocker psreports(unhealthy), the PMS process inside the container has not started or has crashed — checkdocker logs plexfirst.
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 setPLEX_UIDandPLEX_GIDto 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
/configon 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/portsmappings — that is why bridge mode also needsADVERTISE_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
publicandbetatags 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).
-
Visit plex.tv/claim while logged in. Copy the
claim-...token. -
Within four minutes, run the Quick Start command from section 1 (or paste the token into your compose file and
docker compose up -d). -
Watch the logs for the claim handshake:
docker logs -f plex 2>&1 | grep -i claimAttempting to obtain server token from claim token Token obtained successfully -
Open
http://<host-ip>:32400/web. The web UI should already show Welcome to Plex — finish the wizard (server name, libraries, remote access). -
Verify:
docker logs plexends withPlex 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.
-
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 -
Bind-mount it as
/datain your compose file:volumes: - /opt/plex/config:/config - /opt/plex/transcode:/transcode - /mnt/nas-media:/data:ro -
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/configon 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:latestrather than thepublic/betatags. 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.
-
Stop the container so SQLite is quiescent:
docker stop plex -
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 . -
Restart:
docker start plex
Caution
Do not copy
/configwhile 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.
-
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
- Linux package:
-
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). -
Recreate the expected layout under your container
/confighost 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 -
Bring up the container with that path mounted as
/config— and without aPLEX_CLAIM(the database is already claimed):docker compose up -d -
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
plexuser defaults to UID/GID1000. Adjust thechowncommand if your native install used a different owner — or setPLEX_UID/PLEX_GIDin 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.
-
Pick a known-good version tag from the Docker Hub tags page (e.g.,
1.43.0.10492-121068a07). -
Update your compose file:
services: plex: image: plexinc/pms-docker:1.43.0.10492-121068a07 -
Recreate the container:
docker compose up -d -
Verify with
docker exec plex curl -s http://localhost:32400/identity— theversion=attribute should match the pinned tag.
Tip
Pinned tags are also the right choice for offline or air-gapped installs. Both
publicandbetareach out toplex.tvon 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)
- Container Manager > Registry, search
plexinc/pms-docker, downloadlatest(or a pinned tag). - Create a container, use bridge networking, map ports
32400/tcp,1900/udp,32400-32414/udp. - Mount three host folders to
/config,/transcode,/data. - Add environment variables
TZ,PLEX_CLAIM,ADVERTISE_IP=http://<dsm-ip>:32400/. - Run; complete setup at
http://<dsm-ip>:32400/web.
unRAID
- Apps > Community Applications > search "Plex Media Server"
and pick the official
plexinc/pms-dockertemplate (the repo shipsplex-unRAID.xmlfor this). - The template defaults to host networking and pre-configures
/config,/transcode,/data,PLEX_CLAIM,PLEX_UID=99,PLEX_GID=100, andVERSION. - Set
PLEX_CLAIM, point/dataat your/mnt/user/mediashare, and click Apply.
Note
Synology's default Container Manager UI does not support
--network=host— you have to use bridge mode and setADVERTISE_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:
- Check connectivity from inside:
docker exec plex curl -I https://plex.tv - If using
beta, confirm the linked Plex account has Plex Pass. - 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:
- Restart:
docker restart plex(transient network issues recover). - Persistent? Build locally with
--build-arg URL=https://...and a known-good.debURL.
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:
- Stop the container.
- Move
/configto local block storage (ext4, XFS, ZFS, or a Docker named volume on the same). - 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:
- Set
ADVERTISE_IP=http://<host-public-or-LAN-ip>:32400/in the container env. - Forward TCP
32400on your router to the Docker host. - 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:
- On the host:
id $(whoami)— noteuid=1000(jane) gid=1000(jane). - Set
PLEX_UID=1000,PLEX_GID=1000in the env block. - Restart the container — the
40-plex-first-runscript re-runsusermod/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:
-
Add
PLEX_CLAIMto the compose env block and recreate; or -
SSH-tunnel to the server:
ssh user@server -L 32400:server:32400 -NThen open
http://localhost:32400/webon 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 runscont-init.d/50-plex-updateon every start, querieshttps://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.