Skip to content

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 deck user 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/media and bind-mounted read-only into Plex as /media.
  • NAS mounts are declared in /etc/fstab using 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 required inside 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 denied with runuser: run cd /home/deck first or set HOME=/home/deck.
  • Plex starts before the NAS automount is ready: keep the ExecStartPre wait 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:

  1. Open Steam in Desktop Mode.
  2. Go to Games > Add a Non-Steam Game to My Library.
  3. Select Plex HTPC. If it is not listed, browse to the launcher or wrapper script installed by the package.
  4. Add it to Steam and optionally rename the shortcut to Plex.
  5. Return to Gaming Mode or Big Picture Mode.
  6. Open Library > Non-Steam, then launch Plex.

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.