Part 14: When the Platform Leaves the Rack
Part 8 built the machinery for publishing a service: a tunnel, a wildcard, a Caddy block, an identity check. Adding a public hostname became a two-line change. This post is about four services where I did not add one.
DNS filtering, home automation, Mac backups and a Sonos bridge all run on the platform now. None has a Caddy route. None has a public DNS record. The reasons differ in each case, and together they form the other half of the publishing rule.
Why Some Services Stay Home
Three distinct reasons came up, and it is worth separating them.
The service is infrastructure that other things depend on. DNS is the clearest case. If the resolver depends on the tunnel, and the tunnel depends on DNS resolving, a restart can leave the house unable to look up anything. Foundational services need fewer dependencies than the things above them.
The protocol does not survive the trip. Sonos speakers and Time Machine both rely on local discovery — mDNS, SMB advertisements, broadcast traffic. These were designed for a single LAN segment. An HTTPS tunnel carries none of it.
The data is high-value and the convenience is low. A full Mac backup contains everything. Remote access to it buys very little, since restoring over the internet is impractical anyway, while the exposure is total.
Pi-hole: Its Own Container, on Purpose
Pi-hole filters DNS for the household, gives query visibility, and serves local DNS records. It runs in unprivileged LXC 103, separate from the Docker host that runs everything else in this series.
That separation is deliberate. Every other application shares one Docker LXC, which is efficient — and it means an update to that host affects every service at once. DNS failing takes the house offline in a way that a photo service failing does not. So Pi-hole gets its own guest, 1 core, 512MB of RAM, an 8GB disk, and a startup order that puts it ahead of ordinary application guests.
The rollout is still deliberately partial. DHCP stays on the household router, upstreams are Cloudflare’s resolvers, and the trial is pointed at one Mac rather than the whole house. A DNS cutover for everyone is the kind of change where a mistake is noticed by every person in the building at once, so it waits until direct DNS and restart recovery are both proven. Both now are.
Home Assistant: A VM, Not a Container
Home Assistant runs as Proxmox VM 104, with 2 vCPU, 4GB of RAM and a 64GB disk, using UEFI firmware on a Q35 machine.
A VM rather than a container, because Home Assistant OS is an appliance operating system with its own supervisor and add-on system. The project ships it as a virtual appliance image, and the supported artefact avoids assembling an unsupported equivalent from containers. Part 2 chose Proxmox partly so that real VMs are available when something needs one. This is a second instance of that paying off, after the web development VM in Part 10.
Two settings in its guest configuration matter more than the specification. It has onboot=1 with a startup order and a 60-second delay, so it comes back after a host reboot in a predictable sequence. And it has deletion protection enabled, which is the Proxmox setting that refuses to destroy a guest without an explicit override.
Time Machine: The Most Careful Thing on the Platform
Three Macs back up to the platform over SMB, into an isolated Samba LXC with a repurposed 4TB disk behind it. Each Mac gets its own share, its own account, and a size limit:
| Share | Account | Limit |
|---|---|---|
TimeMachine-Studio |
tmstudio |
2 TB |
TimeMachine-Mini |
tmmini |
600 GB |
TimeMachine-Laptop |
tmlaptop |
500 GB |
Each account can write only into its own 0700 directory, anonymous access is disabled, and the verification included a cross-share check confirming that each account is denied access to the others. The quotas exist so one Mac’s runaway backup cannot consume the space the other two need.
Before any of that, the disk itself had to earn the job. It came out of a NAS bay, so it was gated on an extended SMART test completing with zero reallocated, pending, offline-uncorrectable and CRC counts. Its previous contents were erased only after confirming the exact drive serial and the presence of two separately checksum-verified recovery copies. Reusing a disk is cheap. Reusing the wrong disk is not recoverable.
Two failure modes, closed in config
The interesting engineering here is in what happens when things are almost right.
If the USB disk is missing at boot, /mnt/time-machine is an ordinary empty directory on the Proxmox root filesystem. Samba starts happily and writes backups into it, which slowly fills the host’s root disk while every Mac reports success. The fix is a systemd drop-in on the Proxmox host that makes the container’s startup require /mnt/time-machine to be a real mount point. No disk, no container, visible failure.
The second case came from real restarts in August. Samba started before DHCP finished, bound only to loopback, and both Macs lost access to TCP 445 — with the service reporting itself as running. A second drop-in orders Samba after the container’s networking and waits up to 30 seconds for a global IPv4 address on eth0.
The disk is mounted by UUID through /etc/fstab with noatime, nofail and a finite device timeout, so a missing or slow USB disk delays boot briefly rather than hanging it.
Both drop-ins are tracked in the repository, in line with Part 9. Neither is the kind of config anyone reconstructs from memory a year later.
Bonob: Navidrome in a Sonos System
The last one is small and shows the protocol argument clearly. The house has four Sonos speakers on the legacy S1 platform. Bonob is a bridge that presents the Navidrome library from Part 11 as a music service inside the S1 controller app.
S1 speakers find services through local discovery. The seed speaker was found through the _sonos._tcp service on the LAN, and four speakers responded. None of that crosses a tunnel, so a public hostname adds exposure and solves nothing.
Its security boundary is tight for a service nobody outside the house can reach:
- Bound only to the Docker host’s LAN address on its own port
- Runs as the image’s unprivileged
nobodyaccount, read-only root filesystem, dropped capabilities, no-new-privileges - Reaches Navidrome through its LAN binding rather than the public Cloudflare Access route
- Request logging for the music service is switched off, because those logs record listening history
- Its secret lives in the runtime environment file and the password manager, never in the repository
That last pair matters more than it looks. A LAN-only service is not a trusted service, and “nobody can reach it from outside” is not a reason to log what everyone in the house is playing.
The Rule That Emerged
Going through these four, the test that keeps working is: what does a public hostname actually buy, and what does it cost if the service is compromised?
For Forgejo in Part 7, the answer was obvious — pushing from anywhere is the point. For a DNS resolver, a backup target, and a speaker bridge, the honest answer is almost nothing, against a real cost. The wildcard tunnel makes publishing easy enough that this question has to be asked deliberately, every time.
What This Stage Delivered
The platform now serves the house as well as the workshop: filtered DNS, home automation, three Macs backing up automatically, and the music library playing through speakers that predate most of the stack. None of it is reachable from the internet, and the services that need local discovery get a flat LAN to do it on.
Still open: the household DNS cutover beyond the single trial machine, and per-user Sonos account linking beyond the first verification.
Next in the Series
Part 15 is the post where nothing gets fixed. A second network card, an active-backup bond for failover, a test that broke guest connectivity, a byte-for-byte rollback — and a switch that turned out not to support what I assumed it did. It is the most useful failure in the series so far, and it is still open.