Part 1 ended with a NAS that was assembled, instrumented, and empty. Here is what followed: the full media stack — Jellyfin, the *arr suite, qBittorrent, Nextcloud — moved from the old i5 (84 W, 2014) to gbnas (6 W). As I write this, 12 containers have been running for more than a week without a single incident.
données media + appdata media data + appdata
Les configs (SQLite, métadonnées) voyagent avec les données — rien à reconstruire. Configs (SQLite, metadata) travel with the data — nothing to rebuild.
12 conteneurs · linuxserver.io 12 containers · linuxserver.io
Mêmes images, nouveaux chemins de montage — et QuickSync disponible pour Jellyfin. Same images, new mount paths — and QuickSync available for Jellyfin.
jellyfin.* · qbit.* · nextcloud.*
Les hostnames publics ne changent pas — seule la cible interne du tunnel change. The public hostnames don't change — only the tunnel's internal target changes.
docker-compose.yml, up -d, bascule des routes du tunnel.
Les utilisateurs n'ont rien vu — même URLs, même contenu, 14× moins de watts.
The whole stack moved with zero reinstall : rsync, adjusting paths
in docker-compose.yml, up -d, cutting over the tunnel routes.
Users saw nothing change — same URLs, same content, 14× fewer watts.
The plan lost against pragmatism: RAID1, not RAIDZ1
Part 1 proudly announced a RAIDZ1 across 3× 6 TB (~12 TB usable). What actually got installed:
sdc 5.5T disk
└─md127 5.5T raid1 /mnt/storage
sdd 5.5T disk
└─md127 5.5T raid1 /mnt/storage
A 2-disk mdadm mirror, ~5.5 TiB usable. No ZFS. What tipped the balance:
- mdadm is boring — in the noble sense: twenty years of production, built into the kernel,
cat /proc/mdstatand you are done diagnosing. It was already the technology of the old NAS: zero mental migration. - ZFS and its appetite: the rule of thumb of ~1 GB of RAM per TB managed sits badly with the N100M’s 16 GB ceiling (a single SO-DIMM slot). Between a starving ARC and RAM for the containers, I chose the containers.
- Two disks in service, empty bays: the Jonsbo N4 has 6 bays. Starting with a simple mirror and growing later (or switching to a real RAIDZ when both the need and the disks are there) is more honest than tying up three disks for a current need of ~2 TB.
The meta lesson: an infrastructure plan is a hypothesis, not a commitment. Documenting the actual choice and its reasons beats bending reality to match the published plan — which is exactly why this part 2 exists.
The final storage split: a 256 GB NVMe for the OS (LVM), a 500 GB SATA SSD mounted on /mnt for the Docker application volumes (configs, SQLite databases, metadata), and the 5.5 TiB mirror on /mnt/storage for the data.
The migration itself: the Docker Compose advantage
This is where part 1’s “Ubuntu Server + Docker Compose” choice pays off. Migrating a Compose stack is three operations with no magic involved:
rsync -avPof the media data and the appdata folders — the Sonarr/Radarr SQLite databases, the Jellyfin metadata, everything travels with the files. Nothing to export, nothing to rebuild.- Adapt the paths in the
docker-compose.yml(mounts point to/mnt/storageand the appdata SSD) anddocker compose up -d. - Flip the routes: the public hostnames (
jellyfin.*,qbit.*,nextcloud.*…) are defined in the Cloudflare Tunnel dashboard and used to point at the old NAS’s IP. Change the target to10.0.0.30— the public URLs, the certificates and the access rules do not move a pixel.
The result on the docker ps side, a week later:
jellyfin lscr.io/linuxserver/jellyfin Up 9 days
sonarr lscr.io/linuxserver/sonarr Up 9 days
radarr lscr.io/linuxserver/radarr Up 9 days
bazarr lscr.io/linuxserver/bazarr Up 9 days
prowlarr lscr.io/linuxserver/prowlarr Up 9 days
qbittorrent lscr.io/linuxserver/qbittorrent Up 9 days
flaresolverr flaresolverr/flaresolverr Up 9 days
nextcloud lscr.io/linuxserver/nextcloud Up 8 days
A bonus from the extra horsepower: the N100 brings a modern QuickSync (AV1 decoding, HEVC encoding) — Jellyfin transcoding now happens in hardware on the iGPU, where the old i5 was grinding through it in software on its heavier profiles.
The surprise guests
A comfortable machine attracts workloads. Two services joined the stack without being in the original plan:
- Home Assistant — home automation deserved better than a precarious existence; it now has a stable home port
- Bookshelf (reading tracker, Hardcover flavour) — the kind of small service you never deploy “because there is nowhere to put it”
That is a second-order effect of the 6 W: the machine runs 24/7 without energy guilt, so every new service has a marginal cost of zero.
The monitoring loop is closed
Everything wired up in part 1 now covers a machine that actually does something: node_exporter reports the RAID1 mirror’s I/O into Grafana (the NAS disk shows up in the cluster audits), smartctl_exporter watches the health of the disks that now carry all the data, and the Portainer agent (bumped to 2.42.0 along the way) gives the container view alongside the K3s workloads.
The old NAS is powered off. 84 W → 6 W continuously, which is roughly 680 kWh saved per year — the kind of number that makes a homelab defensible in the kitchen.
What is left (the real part 3)
The NAS hosts, but it does not yet serve the cluster:
- Backup offload: move the S3 target of the Longhorn backups (MinIO, today 200 Gi on worker-2’s precious SSD) to the NAS mirror
backup.sh→ K8s CronJob pushing the dumps out of the cluster, to/mnt/storage/backups- An NFS StorageClass to offer the cluster capacity-oriented storage alongside Longhorn
- The UPS question: mechanical disks carrying everything do not like hard power cuts
That will be part 3 — when it is actually done, not before. I learned the RAIDZ1 lesson.