A single Intel NUC running Ubuntu Server 24.04 LTS and eleven Docker containers, serving a household: photo backup, Time Machine backups for Macs, network-wide DNS filtering, home automation, document archiving, and a dashboard to find it all.
This repo is the documentation and the operational fixes — the write-up of an actual running system, including the bugs I found in upstream software and the systemd units I wrote to stop them coming back after a reboot.
All addresses, hostnames and credentials in this repo are placeholders.
| Service | What it does | Notes |
|---|---|---|
| Immich | Self-hosted photo & video backup (Google Photos replacement) | Postgres + Redis + server; 3.1 TB library on a dedicated disk |
| TimeNest | Self-hosted Time Machine target (Samba + vfs_fruit + Avahi + web admin) |
Three containers; 293 GB dedicated partition |
| AdGuard Home | Network-wide DNS filtering and ad-blocking for every device | Authoritative DNS for the LAN |
| Home Assistant | Home automation | Host networking for device discovery |
| Papra | Document / receipt archive | |
| Homer | Static dashboard linking every service | |
| Bambu Studio | Containerised 3D-print slicer with a web UI | |
| CasaOS | Host-level app dashboard and native Samba shares | Runs as native systemd services, not a container |
Host OS: Ubuntu 24.04.4 LTS · Runtime: Docker CE from the official .deb repo
(deliberately not the snap, which sandboxes bind mounts and breaks external drives).
| Doc | Contents |
|---|---|
| docs/architecture.md | Hardware, storage layout, networking, container inventory |
| docs/storage.md | Disk partitioning, filesystem choices, fstab and why nofail matters |
| docs/timenest-samba.md | The Samba/Time Machine service, and the upstream bug I found in it |
| docs/port-conflict.md | Two Samba daemons fighting over ports 139/445 — and why the textbook fix failed |
| docs/reboot-resilience.md | The three systemd changes that make the whole thing survive power loss |
| docs/runbook.md | Diagnostic commands, health checks, recovery procedures |
These are the parts I'd actually want to talk through in an interview.
Samba requires two things to authenticate a user: an entry in its password database
(passdb) and a resolvable Unix account (getpwnam()). The TimeNest web UI created the first
but not reliably the second, so every login failed with:
Failed to find a Unix account for <user>
User <user> in passdb, but getpwnam() fails!
check_sam_security: make_server_info_sam() failed with 'NT_STATUS_NO_SUCH_USER'
Fixing it once was easy. The interesting part was that the fix lived in the container's
non-persistent writable layer, so any image update or --force-recreate silently undid it.
I also learned to pin explicit UIDs — a bare useradd picks the next free UID, which orphans the
ownership of every existing backup folder on disk.
→ docs/timenest-samba.md
CasaOS runs its own native smbd/nmbd on 139/445. TimeNest's compose file uses
network_mode: host — deliberately, because Bonjour/mDNS discovery wants real LAN access. Two
daemons cannot both reliably bind 0.0.0.0:445; the OS hands each incoming connection to
whichever one won, so both sets of shares broke intermittently and non-reproducibly.
The textbook fix is an ipvlan network giving each container its own LAN IP. I built it. It failed — because this server is on Wi-Fi, and ipvlan/macvlan are unreliable on most Wi-Fi adapters, which reject frames using a MAC other than the card's associated one. Containers were unreachable even from the host. I rolled it back and moved Samba to bridge networking with explicit port mapping instead, accepting a documented trade-off in Time Machine auto-discovery. → docs/port-conflict.md
Docker could start before /mnt/timenest finished mounting. The bind mount would then create an
empty directory on the root filesystem instead of pointing at the real 293 GB drive — the
service would come up looking healthy while writing backups nowhere useful, and filling the boot
disk. Fixed with a systemd drop-in ordering Docker after the mount unit, plus a oneshot unit that
re-applies the account fix on every boot. Verified with a real reboot test, not just
systemctl status.
→ docs/reboot-resilience.md
docs/ full write-ups of each area
scripts/
timenest-fix-accounts.sh idempotent Unix-account repair, run on every boot
health-check.sh one-shot container / disk / port / DNS health report
systemd/
timenest-fix.service oneshot unit that runs the repair script
wait-for-timenest-drive.conf docker.service drop-in: wait for the mount
- Containers are not a persistence model. Anything you fix with
docker execis gone the next time the container is recreated. Fixes belong in a mounted volume, an image, or a unit that re-applies them. - The correct solution and the solution that works are not always the same. ipvlan was right on paper; Wi-Fi made it wrong in practice. Knowing why it failed is worth more than the fix.
- Boot ordering is a correctness problem, not a convenience one. The failure mode wasn't a crash, it was silent data going to the wrong disk.
- Write it down while you still remember. Everything here was documented as I fixed it, which is the only reason a second pass took minutes instead of a re-diagnosis.