Running Plex on a Steam Machine with Podman and NAS Media¶
This guide describes a SteamOS-friendly Plex setup using rootless Podman, a NAS-mounted media library, and a small boot repair service for SteamOS updates that rewrite /etc/fstab.
It assumes basic Linux admin familiarity: SSH, systemd, NFS, Podman/containers, and editing root-owned files.
Architecture¶
- Plex runs as the
deckuser with a rootless Podman Quadlet container. - Plex config lives locally under
/home/deck/.local/share/plex-config. - Media is mounted from a NAS at
/mnt/nas/mediaand bind-mounted read-only into Plex as/media. - NAS mounts are declared in
/etc/fstabusing systemd automounts. - A root systemd service runs at boot to recreate the NAS fstab entries if SteamOS removes them during an update.
- A weekly user timer pulls the latest Plex image and restarts the Plex user service.
- A weekly user timer backs up the Plex config directory to the NAS backups mount.
NAS Exports¶
Example NAS exports used by this setup:
192.168.0.3:/volume1/media /mnt/nas/media
192.168.0.3:/volume1/backups /mnt/nas/backups
192.168.0.3:/volume1/downloads /mnt/nas/downloads
192.168.0.3:/volume1/transfer /mnt/nas/transfer
Verify the Steam Machine can see the exports:
showmount -e 192.168.0.3
Root NAS Setup Script¶
Create /home/deck/setup-steammachine-nas.sh:
#!/usr/bin/env bash
set -euo pipefail
mounts=(
"192.168.0.3:/volume1/media /mnt/nas/media nfs vers=3,rw,hard,noatime,_netdev,nofail,x-systemd.automount,x-systemd.idle-timeout=10min,x-systemd.requires=network-online.target,x-systemd.after=network-online.target,x-systemd.mount-timeout=30s 0 0"
"192.168.0.3:/volume1/backups /mnt/nas/backups nfs vers=3,rw,hard,noatime,_netdev,nofail,x-systemd.automount,x-systemd.idle-timeout=10min,x-systemd.requires=network-online.target,x-systemd.after=network-online.target,x-systemd.mount-timeout=30s 0 0"
"192.168.0.3:/volume1/downloads /mnt/nas/downloads nfs vers=3,rw,hard,noatime,_netdev,nofail,x-systemd.automount,x-systemd.idle-timeout=10min,x-systemd.requires=network-online.target,x-systemd.after=network-online.target,x-systemd.mount-timeout=30s 0 0"
"192.168.0.3:/volume1/transfer /mnt/nas/transfer nfs vers=3,rw,hard,noatime,_netdev,nofail,x-systemd.automount,x-systemd.idle-timeout=10min,x-systemd.requires=network-online.target,x-systemd.after=network-online.target,x-systemd.mount-timeout=30s 0 0"
)
mkdir -p /mnt/nas/media /mnt/nas/backups /mnt/nas/downloads /mnt/nas/transfer
cp -n /etc/fstab "/etc/fstab.pre-nas-$(date +%Y%m%d-%H%M%S).bak"
for entry in "${mounts[@]}"; do
target_and_rest=${entry#* }
target_path=${target_and_rest%% *}
if ! awk -v target="$target_path" '$1 !~ /^#/ && $2 == target { found=1 } END { exit !found }' /etc/fstab; then
printf '%s\n' "$entry" >> /etc/fstab
fi
done
systemctl daemon-reload
if ! mountpoint -q /mnt/nas/media; then
mount /mnt/nas/media
fi
if ! mountpoint -q /mnt/nas/backups; then
mount /mnt/nas/backups
fi
mountpoint -q /mnt/nas/media
mountpoint -q /mnt/nas/backups
findmnt /mnt/nas/media || true
findmnt /mnt/nas/backups || true
Set permissions:
chmod 0755 /home/deck/setup-steammachine-nas.sh
chown deck:deck /home/deck/setup-steammachine-nas.sh
The script is idempotent. It creates mountpoint directories, backs up /etc/fstab, and only appends an fstab line when that target is missing.
SteamOS fstab Repair Service¶
SteamOS updates can rewrite /etc/fstab, removing custom NAS entries. Use a root system service to restore them at boot.
Create /etc/systemd/system/ensure-nas-mounts.service:
[Unit]
Description=Ensure Steam Machine NAS mounts
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
ExecStart=/home/deck/setup-steammachine-nas.sh
RemainAfterExit=yes
TimeoutStartSec=120
[Install]
WantedBy=multi-user.target
Enable and run it:
systemctl daemon-reload
systemctl enable ensure-nas-mounts.service
systemctl restart ensure-nas-mounts.service
systemctl --no-pager status ensure-nas-mounts.service
Verify the mount:
findmnt /mnt/nas/media
mountpoint /mnt/nas/media
Plex Quadlet Container¶
Create /home/deck/.config/containers/systemd/plex.container:
[Unit]
Description=Plex Media Server
After=network-online.target
Wants=network-online.target
[Container]
Image=lscr.io/linuxserver/plex:latest
ContainerName=plex
Network=host
SecurityLabelDisable=true
Environment=PUID=1000
Environment=PGID=1000
Environment=TZ=Australia/Sydney
Environment=VERSION=docker
Volume=%h/.local/share/plex-config:/config
Volume=/mnt/nas/media:/media:ro
[Service]
ExecStartPre=/usr/bin/bash -lc 'for i in {1..60}; do /usr/bin/ls /mnt/nas/media >/dev/null 2>&1 && /usr/bin/mountpoint -q /mnt/nas/media && exit 0; /usr/bin/sleep 2; done; exit 1'
TimeoutStartSec=150
Restart=always
RestartSec=10
[Install]
WantedBy=default.target
The ExecStartPre wait prevents Podman from failing immediately if the NAS automount has not triggered yet.
Enable lingering for the deck user so rootless user services can start without an active desktop login:
loginctl enable-linger deck
Enable and start Plex as deck:
runuser -u deck -- env HOME=/home/deck XDG_RUNTIME_DIR=/run/user/1000 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus systemctl --user daemon-reload
runuser -u deck -- env HOME=/home/deck XDG_RUNTIME_DIR=/run/user/1000 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus systemctl --user enable plex.service
runuser -u deck -- env HOME=/home/deck XDG_RUNTIME_DIR=/run/user/1000 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus systemctl --user restart plex.service
Check status:
runuser -u deck -- env HOME=/home/deck XDG_RUNTIME_DIR=/run/user/1000 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus systemctl --user --no-pager status plex.service
cd /home/deck && runuser -u deck -- env HOME=/home/deck podman ps --filter name=plex
If runuser reports cannot chdir to /root: Permission denied, run cd /home/deck first or set HOME=/home/deck as shown above.
Weekly Plex Image Update¶
Create /home/deck/.local/bin/update-plex.sh:
#!/usr/bin/env bash
set -euo pipefail
MEDIA_DIR="/mnt/nas/media"
CONTAINER_NAME="plex"
SERVICE_NAME="plex.service"
IMAGE="lscr.io/linuxserver/plex:latest"
log() {
printf '[plex-container-update] %s\n' "$*"
}
log "Starting update at $(date)"
if ! mountpoint -q "$MEDIA_DIR"; then
log "ERROR: $MEDIA_DIR is not mounted"
exit 1
fi
log "Pulling $IMAGE"
podman pull "$IMAGE"
log "Restarting $SERVICE_NAME"
systemctl --user stop "$SERVICE_NAME" || true
systemctl --user daemon-reload
systemctl --user start "$SERVICE_NAME"
podman image prune -f
podman ps --filter "name=$CONTAINER_NAME"
log "Finished update at $(date)"
Set permissions:
chmod 0755 /home/deck/.local/bin/update-plex.sh
chown deck:deck /home/deck/.local/bin/update-plex.sh
Create /home/deck/.config/systemd/user/update-plex.service:
[Unit]
Description=Update Plex Podman container
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=%h/.local/bin/update-plex.sh
Create /home/deck/.config/systemd/user/update-plex.timer:
[Unit]
Description=Run Plex container update weekly
[Timer]
OnCalendar=Sun 05:15
Persistent=true
RandomizedDelaySec=30m
[Install]
WantedBy=timers.target
Enable the timer:
runuser -u deck -- env HOME=/home/deck XDG_RUNTIME_DIR=/run/user/1000 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus systemctl --user daemon-reload
runuser -u deck -- env HOME=/home/deck XDG_RUNTIME_DIR=/run/user/1000 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus systemctl --user enable --now update-plex.timer
Weekly Plex Config Backup¶
Back up the Plex config directory, not the media library. This captures the Plex database, preferences, metadata, watched state, and container-side configuration under /home/deck/.local/share/plex-config.
This example briefly stops Plex while the archive is created. That avoids copying SQLite databases while Plex is writing to them.
Create /home/deck/.local/bin/backup-plex-config.sh:
#!/usr/bin/env bash
set -euo pipefail
CONFIG_DIR="/home/deck/.local/share/plex-config"
BACKUP_ROOT="/mnt/nas/backups/plex-steammachine"
SERVICE_NAME="plex.service"
STAMP="$(date +%Y%m%d-%H%M%S)"
HOST="$(hostname 2>/dev/null || echo steammachine)"
ARCHIVE="${BACKUP_ROOT}/${HOST}-plex-config-${STAMP}.tar.gz"
TMP_ARCHIVE="${ARCHIVE}.tmp"
RETENTION_DAYS=90
log() {
printf '[plex-config-backup] %s\n' "$*"
}
log "Starting backup at $(date)"
if [ ! -d "$CONFIG_DIR" ]; then
log "ERROR: config directory does not exist: $CONFIG_DIR"
exit 1
fi
# Touch the path first so systemd automount has a chance to mount it.
ls /mnt/nas/backups >/dev/null 2>&1 || true
if ! mountpoint -q /mnt/nas/backups; then
log "ERROR: /mnt/nas/backups is not mounted"
exit 1
fi
mkdir -p "$BACKUP_ROOT"
was_active=0
if systemctl --user is-active --quiet "$SERVICE_NAME"; then
was_active=1
log "Stopping $SERVICE_NAME for a consistent backup"
systemctl --user stop "$SERVICE_NAME"
fi
cleanup() {
rm -f "$TMP_ARCHIVE"
if [ "$was_active" -eq 1 ]; then
systemctl --user start "$SERVICE_NAME" || true
fi
}
trap cleanup EXIT
log "Writing $ARCHIVE"
podman unshare tar -C "$(dirname "$CONFIG_DIR")" -czf "$TMP_ARCHIVE" "$(basename "$CONFIG_DIR")"
mv "$TMP_ARCHIVE" "$ARCHIVE"
find "$BACKUP_ROOT" -type f -name '*-plex-config-*.tar.gz' -mtime +"$RETENTION_DAYS" -delete
trap - EXIT
if [ "$was_active" -eq 1 ]; then
log "Restarting $SERVICE_NAME"
systemctl --user start "$SERVICE_NAME"
fi
log "Finished backup at $(date)"
Set permissions:
chmod 0755 /home/deck/.local/bin/backup-plex-config.sh
chown deck:deck /home/deck/.local/bin/backup-plex-config.sh
Create /home/deck/.config/systemd/user/backup-plex-config.service:
[Unit]
Description=Back up Plex config to NAS
After=network-online.target plex.service
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=%h/.local/bin/backup-plex-config.sh
TimeoutStartSec=30min
Create /home/deck/.config/systemd/user/backup-plex-config.timer:
[Unit]
Description=Run Plex config backup weekly
[Timer]
OnCalendar=Sun 04:30
Persistent=true
RandomizedDelaySec=20m
[Install]
WantedBy=timers.target
Enable the timer:
runuser -u deck -- env HOME=/home/deck XDG_RUNTIME_DIR=/run/user/1000 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus systemctl --user daemon-reload
runuser -u deck -- env HOME=/home/deck XDG_RUNTIME_DIR=/run/user/1000 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus systemctl --user enable --now backup-plex-config.timer
Run one manual backup:
runuser -u deck -- env HOME=/home/deck XDG_RUNTIME_DIR=/run/user/1000 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus systemctl --user start backup-plex-config.service
ls -lh /mnt/nas/backups/plex-steammachine
Troubleshooting¶
Check the NAS repair service:
systemctl --no-pager status ensure-nas-mounts.service
journalctl -u ensure-nas-mounts.service -b --no-pager
Check Plex:
runuser -u deck -- env HOME=/home/deck XDG_RUNTIME_DIR=/run/user/1000 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus systemctl --user --no-pager status plex.service
journalctl --user -u plex.service -b --no-pager
Check mounts:
findmnt /mnt/nas/media
mountpoint /mnt/nas/media
grep '/mnt/nas' /etc/fstab
Check backup jobs:
runuser -u deck -- env HOME=/home/deck XDG_RUNTIME_DIR=/run/user/1000 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus systemctl --user --no-pager status backup-plex-config.timer backup-plex-config.service
journalctl --user -u backup-plex-config.service -b --no-pager
ls -lh /mnt/nas/backups/plex-steammachine
Common failure modes:
statfs /mnt/nas/media: no such file or directory: the mountpoint directory does not exist or the fstab repair service has not run.sudo: a password is requiredinside a user service: avoid calling sudo from the user service; run the NAS repair as a root system service instead.cannot chdir to /root: Permission deniedwithrunuser: runcd /home/deckfirst or setHOME=/home/deck.- Plex starts before the NAS automount is ready: keep the
ExecStartPrewait in the Quadlet. - Backup service reports
/mnt/nas/backups is not mounted: confirm the NAS repair service restored the backups fstab entry and that the NAS export allows the Steam Machine IP.
Operational Notes¶
After SteamOS updates, verify:
systemctl --no-pager status ensure-nas-mounts.service
findmnt /mnt/nas/media
runuser -u deck -- env HOME=/home/deck XDG_RUNTIME_DIR=/run/user/1000 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus systemctl --user --no-pager status plex.service
The important persistence rule is: do not rely on /etc/fstab alone on SteamOS. Keep the source of truth in /home/deck/setup-steammachine-nas.sh, and let the root system service recreate fstab entries whenever the OS removes them.
Finally, if you want to stream Plex media from other devices, configure the Steam Machine through the GUI so it does not enter sleep mode when idle. Also disable "Sleep when TV is powered off" under the HDMI-CEC settings.
Local Playback with Plex HTPC¶
For using Plex directly on the Steam Machine, install Plex HTPC as the living-room client. The Plex server can run in the background via the Podman service described above, while Plex HTPC provides the TV-friendly playback UI.
Install Plex HTPC from Desktop Mode using Discover/Flathub if available, or from the official Plex HTPC package for Linux. After installation, launch it once from Desktop Mode and sign in to the same Plex account used by the server.
To make it available from Steam Big Picture:
- Open Steam in Desktop Mode.
- Go to
Games>Add a Non-Steam Game to My Library. - Select
Plex HTPC. If it is not listed, browse to the launcher or wrapper script installed by the package. - Add it to Steam and optionally rename the shortcut to
Plex. - Return to Gaming Mode or Big Picture Mode.
- Open
Library>Non-Steam, then launchPlex.
Once added, Plex HTPC behaves like any other non-Steam app in Big Picture Mode. It can be pinned as a favorite, launched with the controller, and used as the front-end for the local Plex server or any other Plex server on the account.