Storage & Docker on Linux¶
The Linux equivalent of NAS base setup. Paths used on this page:
| Synology | Old PC (this page) | On |
|---|---|---|
/volume1/data |
/mnt/storage/data |
data HDD(s) |
/volume1/docker |
/opt/docker |
OS SSD (fast for app databases) |
1. Mount the data disks¶
Find the disks and create a filesystem (this erases them):
lsblk -o NAME,SIZE,MODEL,SERIAL,FSTYPE,MOUNTPOINT
sudo mkfs.ext4 -L data1 /dev/sdX # or: sudo mkfs.xfs -L data1 /dev/sdX (apt install xfsprogs)
sudo blkid /dev/sdX # copy the UUID
Mount it by UUID in /etc/fstab:
UUID=<DISK_UUID> /mnt/disk1 ext4 defaults,noatime,nofail 0 2
sudo mkdir -p /mnt/disk1 && sudo systemctl daemon-reload && sudo mount -a && df -h /mnt/disk1
nofail lets the box boot (and you SSH in) even if a disk dies. ext4 and XFS are both fine for media; pick one.
Pick a layout for more than one disk¶
Mount it straight at /mnt/storage. Simple. No redundancy, so the restic backups and your ability to re-download are the safety net.
Each disk keeps its own ext4/XFS filesystem (/mnt/disk1, /mnt/disk2, …); mergerfs pools them into /mnt/storage, and SnapRAID adds scheduled parity on one extra disk at least as large as the biggest data disk.
sudo apt install mergerfs snapraid
/mnt/disk* /mnt/storage fuse.mergerfs cache.files=off,category.create=epmfs,moveonenospc=true,dropcacheonclose=true,minfreespace=50G,fsname=mergerfs,nofail 0 0
- Good: mix any sizes, add disks one at a time, a dead disk only loses its own files.
- Catch: parity is only as fresh as the last
snapraid sync(run it from a systemd timer). - Hardlinks only work within one disk.
epmfs(existing-path) puts new files on a disk where the parent folder already exists, so a download and its hardlink stay together. Create thedata/tree on each disk, and test withls -li. See the mergerfs docs. - The Debian/Ubuntu
mergerfspackage can be old; the project publishes current.debfiles on its GitHub releases.
sudo apt install zfsutils-linux # Debian: enable contrib and install zfs-dkms + zfsutils-linux
sudo zpool create -o ashift=12 -O compression=lz4 -O atime=off -m /mnt/storage tank mirror /dev/disk/by-id/<DISK_A> /dev/disk/by-id/<DISK_B>
- Good: real-time redundancy, checksums/self-healing, cheap snapshots (closest to Btrfs + Snapshot Replication on DSM).
- Catch: drives should be the same size, growing means adding another mirror pair, and it likes RAM. Don't put ZFS in
/etc/fstab; ZFS mounts the pool itself.
2. Users and PUID/PGID¶
sudo groupadd -g 1000 media 2>/dev/null || true # or reuse an existing group; note its GID
sudo useradd -r -M -s /usr/sbin/nologin -g media <media_user>
sudo useradd -r -M -s /usr/sbin/nologin -g media <plex_user>
id <media_user>; id <plex_user> # → ${PUID}, ${PLEX_PUID}, ${PGID}
On Synology the shared group is users (100). Here use the group you created; set PGID to its GID in every stack. Keep UMASK=002.
3. The /data folder structure (same as the NAS)¶
sudo mkdir -p /mnt/storage/data/{torrents/{incomplete,movies,tv,watch},usenet/{incomplete,complete/{movies,tv}},media/{movies,tv}}
sudo chown -R <media_user>:media /mnt/storage/data
sudo chmod -R a=,a+rX,u+w,g+w /mnt/storage/data # 775 dirs / 664 files
sudo mkdir -p /opt/docker && sudo chown <media_user>:media /opt/docker
torrents and media must be on the same filesystem for hardlinks and instant moves. See TRaSH's
Hardlinks and Instant Moves.
4. Docker Engine + compose plugin¶
Use Docker's own repository, not docker.io from the distro or the snap. From the official
Ubuntu / Debian install pages:
sudo apt remove $(dpkg --get-selections docker.io docker-compose docker-compose-v2 docker-doc podman-docker containerd runc | cut -f1)
sudo apt update && sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
sudo tee /etc/apt/sources.list.d/docker.sources <<SRC
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
SRC
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo apt remove $(dpkg --get-selections docker.io docker-compose docker-doc podman-docker containerd runc | cut -f1)
sudo apt update && sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
sudo tee /etc/apt/sources.list.d/docker.sources <<SRC
Types: deb
URIs: https://download.docker.com/linux/debian
Suites: $(. /etc/os-release && echo "$VERSION_CODENAME")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
SRC
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo docker run --rm hello-world
docker compose version # v2 plugin: `docker compose`, not `docker-compose`
Optional: sudo usermod -aG docker $USER (log out and in). Members of docker are effectively root.
5. Portainer¶
Same as Portainer, with the Linux config path:
sudo docker run -d --name portainer-ce --restart unless-stopped \
-p 9443:9443 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /opt/docker/portainer-ce:/data \
portainer/portainer-ce:lts
Open https://<SERVER_IP>:9443 within a few minutes (the first-run setup times out) and create the admin user.
6. Plex hardware transcoding (Quick Sync)¶
Add the iGPU to the Plex stack:
devices:
- /dev/dri:/dev/dri
The linuxserver image gives its internal user access to /dev/dri automatically. Check the host side:
ls -l /dev/dri/renderD128 # group is usually "render"
getent group render # if Plex can't open the device: add `group_add: ["<render_GID>"]` to the service
Then in Plex (Plex Pass required): Settings → Transcoder → Use hardware acceleration when available ON, Use hardware-accelerated video encoding ON, Enable HDR tone mapping ON. Play something with Convert automatically to a lower quality and look for (hw) in the Dashboard. Details: Plex's hardware-accelerated streaming article.
Note
With working Quick Sync you can relax the Direct-Play-only tuning on the Plex page (for example allow more than 1 transcode). The client-side "Original quality" advice still saves quality and bandwidth.
7. Firewall (ufw)¶
sudo apt install ufw
sudo ufw default deny incoming && sudo ufw default allow outgoing
sudo ufw allow from ${LAN_SUBNET} to any port 22 proto tcp # SSH, LAN only
sudo ufw allow 32400/tcp # Plex (host network; remote access)
sudo ufw enable && sudo ufw status verbose
Docker bypasses ufw for published ports
Ports published with ports: (Portainer 9443, Sonarr, Radarr, Seerr…) are opened by Docker's own iptables rules and ignore ufw.
Keep them off the internet by not forwarding them on the router, or bind them to the LAN address ("<SERVER_IP>:9443:9443").
Containers on network_mode: host (Plex) are filtered by ufw. See Docker's Docker and ufw.
8. Synology → Linux mapping¶
Every stack page works unchanged apart from these substitutions:
| On the Synology pages | On the old PC |
|---|---|
/volume1/data (share data) |
/mnt/storage/data |
/volume1/docker (share docker) |
/opt/docker |
DSM users, group users (100) |
Linux users, your media group's GID |
Container Manager, /usr/local/bin/docker, old docker-compose v1 |
Docker Engine from docker.com, docker compose v2 |
| DSM Task Scheduler (scripts, for example a qBittorrent alt-speed toggle if you script it instead of using qBittorrent's built-in scheduler) | systemd timer (or root crontab -e) |
Snapshot Replication (#snapshot folders) |
ZFS snapshots (zfs snapshot, or sanoid), Btrfs snapshots (snapper) if you chose Btrfs, or skip on ext4/XFS and rely on restic |
| Hyper Backup | N/A, use restic → B2 as in the guide |
| DSM firewall | ufw (above) |
| DSM storage/SMART alerts | smartmontools (smartd), zed for ZFS |
| SHR / Storage Manager → Expand | mergerfs (+SnapRAID) or a ZFS mirror (above) |
systemd timer instead of a DSM scheduled task
[Unit]
Description=Media stack scheduled task
[Service]
Type=oneshot
ExecStart=/opt/docker/scripts/<script>.sh
[Unit]
Description=Run media-task on a schedule
[Timer]
OnCalendar=*-*-* <HH>:<MM>:00
Persistent=true
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload && sudo systemctl enable --now media-task.timer
systemctl list-timers media-task.timer
Then carry on with the build order from step 3 (restore, then deploy the stacks).