| your Linux construction kit
Source

Appliance Recipe: Plex Media Server on ZFS

A kldloadOS media server where every movie gets its own ZFS dataset. Clone libraries instantly. Replicate content to a second server with zfs send. Compress, snapshot, and manage petabytes of media the way Netflix’s Open Connect CDN does it — individual content datasets replicated to edge nodes.

Why per-movie datasets matter:

  • zfs send one movie to a friend’s server in seconds
  • Clone a dataset for transcoding without touching the original
  • Different compression per content type
  • Quota per library
  • Snapshot before bulk operations (metadata edits, re-encodes)
  • Replicate your entire library to a backup server incrementally

Architecture

┌──────────────────────────────────────────────────────────────────────┐
│                     kldloadOS Plex Server                             │
│                                                                      │
│  rpool/media                          ← master media dataset         │
│  ├── movies/                          ← container (canmount=off)     │
│  │   ├── the-matrix-1999             ← one dataset per movie         │
│  │   ├── blade-runner-2049                                           │
│  │   ├── dune-2021                                                   │
│  │   └── ...                                                         │
│  ├── tv/                                                             │
│  │   ├── breaking-bad                ← one dataset per series        │
│  │   ├── the-expanse                                                 │
│  │   └── ...                                                         │
│  ├── music/                          ← lz4 compression (lossless)    │
│  └── photos/                         ← compression off (JPEG/RAW)   │
│                                                                      │
│  rpool/plex                          ← Plex metadata + database      │
│  ├── config                          ← recordsize=8k (SQLite)        │
│  └── transcode                       ← temporary, no snapshots       │
│                                                                      │
│  Plex Media Server (:32400)                                          │
│  wg0: WireGuard to backup server                                     │
└──────────────────────────────────────────────────────────────────────┘
        │
        │ syncoid (hourly)
        │ WireGuard tunnel
        ▼
┌──────────────────────────┐
│  kldloadOS Backup/Edge   │
│  rpool/media-replica     │
│  Plex Media Server       │
│  (read-only mirror)      │
└──────────────────────────┘

Step 1: Install kldloadOS

cat > /tmp/answers.env << 'EOF'
KLDLOAD_DISTRO=debian
KLDLOAD_DISK=/dev/sda
KLDLOAD_HOSTNAME=plex-server
KLDLOAD_USERNAME=admin
KLDLOAD_PASSWORD=changeme
KLDLOAD_PROFILE=server
KLDLOAD_NET_METHOD=dhcp
EOF
kldload-install-target --config /tmp/answers.env

Step 2: Create the ZFS dataset hierarchy

# Master media container (no mountpoint — children mount individually)
zfs create -o canmount=off -o mountpoint=none rpool/media

# Movie library — each movie gets its own dataset (created per-movie below)
zfs create -o canmount=off -o mountpoint=/srv/media/movies rpool/media/movies

# TV library — each series gets its own dataset
zfs create -o canmount=off -o mountpoint=/srv/media/tv rpool/media/tv

# Music — lz4 compression (FLAC benefits, MP3 doesn't hurt)
zfs create -o mountpoint=/srv/media/music -o compression=lz4 rpool/media/music

# Photos — compression off (JPEG/HEIF/RAW are already compressed)
zfs create -o mountpoint=/srv/media/photos -o compression=off rpool/media/photos

# Plex application data
zfs create -o mountpoint=/var/lib/plexmediaserver rpool/plex
zfs create -o mountpoint=/var/lib/plexmediaserver/Library -o recordsize=8k rpool/plex/config
zfs create -o mountpoint=/tmp/plex-transcode -o compression=off -o com.sun:auto-snapshot=false rpool/plex/transcode

Step 3: Script to create per-movie datasets

#!/bin/bash
# /usr/local/bin/add-movie
# Usage: add-movie "The Matrix" 1999

TITLE="$1"
YEAR="$2"

if [[ -z "$TITLE" || -z "$YEAR" ]]; then
  echo "Usage: add-movie \"Movie Title\" YEAR"
  exit 1
fi

# Normalize name: lowercase, replace spaces with hyphens
SLUG=$(echo "${TITLE}-${YEAR}" | tr '[:upper:]' '[:lower:]' | tr ' ' '-' | tr -cd 'a-z0-9-')

DATASET="rpool/media/movies/${SLUG}"
MOUNT="/srv/media/movies/${SLUG}"

if zfs list "$DATASET" >/dev/null 2>&1; then
  echo "Dataset already exists: $DATASET"
  exit 1
fi

# Video files are already compressed — disable ZFS compression
zfs create -o mountpoint="$MOUNT" -o compression=off -o recordsize=1M "$DATASET"

# Create Plex-expected directory structure
mkdir -p "${MOUNT}"

echo "Created: $DATASET → $MOUNT"
echo "Copy your movie file into: $MOUNT"
chmod +x /usr/local/bin/add-movie

# Add movies
add-movie "The Matrix" 1999
add-movie "Blade Runner 2049" 2017
add-movie "Dune" 2021
add-movie "Alien" 1979

Same for TV:

#!/bin/bash
# /usr/local/bin/add-series
# Usage: add-series "Breaking Bad"

TITLE="$1"
SLUG=$(echo "$TITLE" | tr '[:upper:]' '[:lower:]' | tr ' ' '-' | tr -cd 'a-z0-9-')
DATASET="rpool/media/tv/${SLUG}"
MOUNT="/srv/media/tv/${SLUG}"

zfs create -o mountpoint="$MOUNT" -o compression=off -o recordsize=1M "$DATASET"
echo "Created: $DATASET → $MOUNT"
echo "Create season directories: mkdir ${MOUNT}/Season\\ {1..5}"

Step 4: Install Plex Media Server

# Debian
curl -fsSL https://downloads.plex.tv/plex-keys/PlexSign.key | \
  gpg --dearmor -o /usr/share/keyrings/plex.gpg

echo "deb [signed-by=/usr/share/keyrings/plex.gpg] https://downloads.plex.tv/repo/deb public main" \
  > /etc/apt/sources.list.d/plexmediaserver.list

apt update
apt install -y plexmediaserver

systemctl enable --now plexmediaserver

Access Plex at: http://plex-server:32400/web

Add libraries: - Movies: /srv/media/movies - TV: /srv/media/tv - Music: /srv/media/music - Photos: /srv/media/photos


Step 5: ZFS operations for media management

Snapshot before bulk operations

# Before re-encoding a movie
zfs snapshot rpool/media/movies/the-matrix-1999@before-reencode

# Before editing metadata for entire library
zfs snapshot -r rpool/media/movies@before-metadata-edit

# Rollback if something goes wrong
zfs rollback rpool/media/movies/the-matrix-1999@before-reencode

Clone for transcoding

# Clone a movie dataset for transcoding — zero copy cost
zfs snapshot rpool/media/movies/dune-2021@transcode-src
zfs clone rpool/media/movies/dune-2021@transcode-src rpool/media/movies/dune-2021-4k-to-1080p

# Transcode in the clone (original untouched)
ffmpeg -i /srv/media/movies/dune-2021-4k-to-1080p/Dune.2021.2160p.mkv \
  -c:v libx264 -preset slow -crf 18 -vf scale=1920:1080 \
  -c:a copy \
  /srv/media/movies/dune-2021-4k-to-1080p/Dune.2021.1080p.mkv

# Keep the clone or destroy it
zfs destroy rpool/media/movies/dune-2021-4k-to-1080p

Send a movie to a friend

# Snapshot the movie
zfs snapshot rpool/media/movies/blade-runner-2049@share

# Send it (full)
zfs send rpool/media/movies/blade-runner-2049@share | \
  ssh friend@their-server zfs receive tank/media/movies/blade-runner-2049

# Or compressed over WireGuard
zfs send rpool/media/movies/blade-runner-2049@share | \
  zstd -3 | ssh 10.200.0.2 "zstd -d | zfs receive tank/media/movies/blade-runner-2049"

Check library disk usage

# Per-movie usage
zfs list -r rpool/media/movies -o name,used,compressratio -S used | head -20

# Total library size
zfs list rpool/media -o name,used,avail,compressratio

# Compression savings (music library should show benefit)
zfs get compressratio rpool/media/music

Step 6: Replicate entire library to a backup server

Initial full replication

# Snapshot everything
zfs snapshot -r rpool/media@replicate-initial

# Send entire library to backup server over WireGuard
zfs send -R rpool/media@replicate-initial | \
  ssh 10.200.0.2 zfs receive -F rpool/media-replica

Daily incremental replication

# Cron job — replicate changes every night
cat > /etc/cron.d/media-replicate << 'EOF'
0 3 * * * root syncoid -r rpool/media 10.200.0.2:rpool/media-replica 2>&1 | logger -t media-replicate
0 3 * * * root syncoid rpool/plex/config 10.200.0.2:rpool/plex-replica/config 2>&1 | logger -t plex-replicate
EOF

The backup server runs Plex too

On the backup server (10.200.0.2):

# Point Plex at the replicated datasets
# Movies: /srv/media-replica/movies
# TV: /srv/media-replica/tv

# This gives you a hot standby — if the primary dies,
# the backup is already serving the same library

Step 7: Quotas per library

# Limit movies to 10TB
zfs set quota=10T rpool/media/movies

# Limit TV to 5TB
zfs set quota=5T rpool/media/tv

# Reserve 2TB for music (guaranteed even if movies fill up)
zfs set reservation=2T rpool/media/music

# Check quotas
zfs get quota,reservation,used rpool/media/movies rpool/media/tv rpool/media/music

Step 8: Automated cleanup

#!/bin/bash
# /usr/local/bin/media-cleanup
# Remove snapshots older than 30 days

zfs list -t snapshot -H -o name,creation -r rpool/media | while read snap creation; do
  snap_epoch=$(date -d "$creation" +%s 2>/dev/null || echo 0)
  cutoff_epoch=$(date -d "30 days ago" +%s)
  if (( snap_epoch < cutoff_epoch && snap_epoch > 0 )); then
    echo "Destroying: $snap ($creation)"
    zfs destroy "$snap"
  fi
done
chmod +x /usr/local/bin/media-cleanup
echo '0 4 * * 0 root /usr/local/bin/media-cleanup' >> /etc/crontab

Netflix comparison

Netflix’s Open Connect appliances use a remarkably similar architecture:

Feature Netflix Open Connect kldloadOS Plex
Storage ZFS on FreeBSD ZFS on Linux
Content datasets Per-title datasets Per-movie datasets
Replication zfs send to edge nodes syncoid to backup/edge
Compression Off (video is pre-compressed) Off for video, lz4 for music
Snapshots Before content updates Before metadata/transcode ops
Edge distribution HTTP from local cache Plex from local replica

The architecture is the same. The scale is different. The principle — individual datasets replicated to edge nodes — is identical.


Bill of materials

Component Cost
Mini PC / server (8+ cores, 16GB RAM) $400-800
Storage (4x 8TB HDD, RAIDZ1) $400-600
GPU for transcoding (optional) $150-300
kldloadOS on USB Free
Plex Pass (lifetime, optional) $120
Total ~$1,000-1,800