Skip to content

Migration from Unraid

This server previously ran Unraid. It now runs Debian 13 with Docker Compose, mergerfs, SnapRAID, and systemd maintenance. This page records what changed and how the cutover was performed; it is not a rebuild guide. The resulting storage layout is described in storage-layout.md and host-facts.md.

Why move at all

The change moved service definitions and operational configuration from UI templates and plugins into Git, Docker Compose, and systemd. Debian also makes the operator responsible for fstab, mergerfs, SnapRAID, backups, updates, and recovery. The main storage trade-off is parity timing: Unraid updates parity in real time, while SnapRAID protects new writes at the next scheduled sync. This was accepted for a mostly write-once media library .

What moved

Piece On Unraid On Debian
Data disks Unraid array + user shares 5 independent XFS disks (2×28 TB, 3×12 TB) pooled by mergerfs at /mnt/user
Parity Real-time array parity 28 TB SnapRAID parity disk, outside the pool, spins down
Cache Unraid btrfs cache pool Same NVMe, reused in place: mounted as-is, never reformatted; appdata rode across on the device itself
Appdata /mnt/cache/appdata /mnt/cache/appdata, same numeric ownership
Containers Community-Apps templates compose.yaml in git
Ownership nobody:users = UID 99 / GID 100 Containers run PUID=99 / PGID=100, matched rather than chowned
GPU transcode Unraid Intel plugin Intel Arc A380 on bare-metal Debian (iHD VA-API driver)
Maintenance Plugins / mover systemd timers (SnapRAID, btrfs scrub, restic, watchdogs)

Two things deliberately did not move. There is no mover: SABnzbd stages incomplete/ on the NVMe so unpack I/O stays off the spinning disks, then post-processing writes the finished file straight to the array. And the Docker data-root stayed on the OS SSD at the default /var/lib/docker rather than following appdata onto the cache. The current engine uses the containerd image store and reports overlayfs; see Host facts.

How the cutover was run

The migration used explicit stop points. Disks were addressed by /dev/disk/by-id/, never /dev/sdX; existing data disks were mounted read-only first; data disks were never formatted; and formatting the old Unraid parity disk as the commit point where easy rollback ends.

  1. Inventory. Record the disk-to-slot map, every container's image, ports, paths, volumes and devices, numeric UID/GID ownership, and the network/ingress dependencies. Secrets as placeholders, real values kept private. This is the work that makes the migration reversible.
  2. Rehearse on Proxmox. The storage and Compose plan was exercised on a VM first, so the cutover night had as few new variables as possible.
  3. Debian prep. Minimal Debian 13, key-only SSH, Docker + Compose, the storage packages, and a translated Compose project that passed docker compose config with a placeholder .env, all before the window.
  4. Clean Unraid shutdown. Pause downloads and imports, stop containers and the array cleanly, and keep the Unraid USB as a rollback boot device.
  5. Read-only validation on Debian. Boot Debian, confirm every disk appears by its stable ID, mount the data and cache disks read-only, verify the expected files are there, and check journalctl for storage errors. Stop and roll back here if anything looks wrong; nothing destructive has happened yet.
  6. Persistent mounts. Add by-id /etc/fstab entries (nofail, with explicit x-systemd.requires-mounts-for= so the mergerfs pool cannot mount before its branch disks and present an empty pool), then bring up the pool read-write.
  7. Commit point. With the pool showing the expected media and the mapping re-checked, format the old parity disk, write snapraid.conf, and start the first snapraid sync. After this, rollback to Unraid is no longer clean.
  8. Services. Mount appdata in place, confirm numeric ownership, fill in the private .env, and bring containers up in dependency order: download clients, indexers, media managers, media server, request apps.

What was validated

The checks that actually mattered, in order:

  • Storage ordering. findmnt -R /mnt shows every branch mounted, and the mergerfs pool never masks a missing disk with an empty directory on /.
  • Path fidelity. Download clients see their old paths so torrents resume without a forced recheck, and media managers keep their existing root folders.
  • Appdata is real, not fresh. Every container reads migrated state, not a new empty config directory. A fresh config dir is the failure mode that looks like data loss.
  • Restore before trust. The appdata backup was restore-tested, not just created. A backup that has never been restored is not yet a backup.
  • Hardware transcode. A real transcode confirmed on the Arc A380 (vainfo, intel_gpu_top), after plain direct play worked first.
  • Parity protection. The first full SnapRAID sync completed, and I made sure I understood its protection-gap window before trusting it.

Lessons recorded

  • Reusing the NVMe cache kept appdata in place and avoided a restore and path migration during the cutover.
  • Matching the existing numeric UID/GID (99:100) preserved permissions and hardlinks without changing the media tree.
  • The Proxmox rehearsal identified most configuration issues before cutover. SATA link resets that appeared only on the physical host were later resolved with a max_performance ALPM rule; see storage-layout.md.
  • SnapRAID leaves new data unprotected until the next sync. Monitoring and the sync schedule make that window explicit.
  • Host configuration and Compose deployment are now converged through Ansible; see the behavior contract.