Host Facts (Debian 13)
Current host-specific values used by the storage and Compose configuration.
The public repository records hardware roles and reusable behavior, not the live deployment map. Credentials stay in ignored
.envfiles. Addresses, hostnames, ports, interface names, disk serials, and by-id device paths stay in the ignored Ansiblesite.ymlor on the host. Filesystem UUIDs remain in/etc/fstabandsnapraid.confon the host. The GIDs below are assigned by the OS at install time, so always re-read them rather than trusting a copy. See Security boundaries.
Disk addressing
sdX letters are not stable across reboots, and they differ from the old Unraid
assignments. Always address disks by /dev/disk/by-id/ (model + serial). See
storage-layout.md for the role table. Find the real links with
ls -l /dev/disk/by-id/.
| Role | Class | Filesystem | Notes |
|---|---|---|---|
| parity | 28 TB SATA HDD | XFS (created at cutover) | outside the mergerfs pool |
| disk1–disk2 | 28 TB SATA HDD | XFS | mergerfs branches |
| disk3–disk5 | 12 TB SATA HDD | XFS | mergerfs branches |
| cache | 2 TB NVMe | btrfs (reused Unraid pool, never mkfs) |
appdata + in-progress usenet downloads (SAB incomplete/ staging) |
| OS | 1 TB SATA SSD | ext4 (/) + EFI + swap |
separate disk, outside the array/cache layout |
- The 1 TB SATA SSD is the Debian OS disk.
- The cache NVMe exposes two
nvme-…by-id names (one with a trailing_1). Both resolve to the same partition. Mount whichever…-part1link actually exists on the host, and never reformat it.
Ownership (media tree)
Unraid wrote all media as numeric UID 99 / GID 100 (nobody:users). To keep
permissions and hardlinks intact, containers run with PUID=99 / PGID=100. Do
not chown the multi-TB tree; matching the IDs is free, rewriting ownership on
that much data is not. The admin login user is a member of group users (100), which
gives it group-level read access to the media.
GPU (Plex hardware transcoding)
- Intel Arc A380, bare metal (drivers, not passthrough).
- Driver stack:
firmware-intel-graphics(non-free-firmware) plusintel-media-va-driver-non-free(non-free), which provides the iHD VA-API driver. Verify withvainfo(expect HEVC Main10 decode + encode, AV1, andVAEntrypointVideoProcfor HDR/DV tone mapping) andintel_gpu_top. - Render node:
/dev/dri/renderD128(crw-rw---- root render), pluscard0. - Containers need the device mapped and the render/video groups added:
devices:
- /dev/dri:/dev/dri
group_add:
- "992" # render - re-verify: getent group render
- "44" # video - re-verify: getent group video
Use numeric GIDs, because the container's /etc/group has no render group. On
this host render=992 and video=44; read them fresh with
getent group render video after any reinstall.
Docker
- data-root: default
/var/lib/dockeron the OS SSD (not moved to the cache). The OS SSD has plenty of room for image layers, and it keeps Docker's storage off the reused btrfs cache. Decided before the first image pull. Note the running engine (Docker 29) uses the containerd image store, sodocker inforeports the storage driver asoverlayfs(the containerd snapshotter), notoverlay2. Side effect: cAdvisor cannot read per-container stats on this setup, which is why per-container CPU/memory comes from the docker-stats textfile collector instead. - Stale systemd scope on
docker restart/stop+start: fails withUnit docker-<id>.scope was already loaded or has a fragment file. Root cause: systemd 257 has not yet garbage-collected the just-stopped container's transient scope when runc recreates a scope with the same name (derived from the container ID, which restart reuses). runc >= 1.1.6 already resets failed units; this GC race variant is still open upstream (systemd#39141, same class as crun#1138 and podman#24010). Prevention: restart by recreating, i.e.docker compose up -d --force-recreate <svc>. A recreate gets a new container ID and therefore a new scope name, so the collision cannot happen. If you usedocker restartanyway, runsudo systemctl daemon-reload(orsystemctl reset-failed 'docker-*.scope') first; the same commands recover after a hit. Do NOT switch to the cgroupfs driver to dodge this: monitoring relies on systemd cgroup paths. - A daemon restart orphans socket bind-mounts, and here it silently kills log
ingestion. When
dockerdrestarts it recreates/run/docker.sockwith a new inode. Any long-running container that bind-mounts the socket keeps pointing at the old, now-dead inode until it is recreated. Onlysocket-proxy-monitoringmounts the socket here; when it goes stale its HAProxy backend returns503to Alloy, so Alloy stops tailing every container and Loki goes quiet while all metrics still look healthy. This bit us on 2026-07-18: an unattendeddocker-ceupgrade (which is intentional, see the APT section) restarted the daemon and log ingestion was dead for ~29h before anyone noticed. Two mitigations are in place: therestart-socket-consumers.servicesystemd unit (PartOf=docker.service, installed by thedocker_hostrole as DOCKER-7) recreates the socket proxy and bounces Alloy whenever the daemon restarts, and theLokiLogIngestionStalledPrometheus alert fires if received log lines hit zero for 20 minutes as a backstop.
APT
Debian 13 (Trixie), classic /etc/apt/sources.list. Components enabled:
main contrib non-free non-free-firmware (needed for the Intel firmware and media
driver).
Access
Admin access is key-only SSH on a non-default port as the sudo login user (passwordless sudo). Root SSH login and password auth are disabled. This hardening was Phase E of the migration and has been in place since the cutover; if you ever rebuild, verify key login works before turning the rest of it on.