homelab
x 0.95 · purpose 0.15 · t 1 yr · scope 0.90
from 2025-09
Linux · virtualization · Docker · PKI
The homelab began as a way to learn Linux by using it for something real. It now runs the services I rely on, this site among them. The interesting part is its shape: a network cut into zones by trust, compute kept apart from storage, and every workload given the isolation it needs and no more.
- vlans
- 7
- hypervisors + nas
- 3 + 1
- vms · lxcs
- 7 · 16
zones, not a flat network
10 zones (7 of them VLANs) against each other, from the router's rules: 28 of 90 ordered pairs are open, each for named purposes; every other pair is denied by default. internet and storage may start nothing. Left and right arrows read one zone at a time.
data
| from | may reach | for | rules | opened to |
|---|---|---|---|---|
| off-site relay | services | logs + metrics, web | 2 | named hosts |
| dmz entry | internet | dns, web | 2 | named hosts |
| dmz entry | off-site relay | tunnel | 2 | named hosts |
| dmz entry | dmz bridge | api | 1 | named hosts |
| dmz entry | dmz web | game, the site | 2 | named hosts |
| dmz entry | services | logs + metrics, app | 2 | named hosts |
| dmz bridge | internet | dns | 1 | named hosts |
| dmz bridge | services | logs + metrics, app, web | 4 | named hosts |
| dmz web | internet | dns, web | 3 | named hosts |
| dmz web | services | logs + metrics, web | 2 | named hosts |
| clients | internet | anything | 1 | the whole zone |
| clients | dmz entry | ssh | 1 | named hosts |
| clients | dmz bridge | ssh | 1 | named hosts |
| clients | dmz web | ssh | 1 | named hosts |
| clients | services | ssh, web | 2 | the whole zone |
| clients | management | web | 1 | named hosts |
| services | internet | web, tunnel, mail, file sync | 9 | the whole zone |
| services | off-site relay | tunnel | 1 | named hosts |
| services | dmz entry | ssh | 1 | named hosts |
| services | dmz web | file sync, ssh | 2 | named hosts |
| services | management | ssh | 1 | named hosts |
| services | storage | file shares | 2 | the whole zone |
| management | internet | off-site copy, tunnel, mail, web | 4 | named hosts |
| management | services | alerts, check-ins, logs + metrics, backups | 5 | the whole zone |
| management | storage | file shares | 1 | named hosts |
| admin vpn | clients | ssh | 1 | named hosts |
| admin vpn | services | ssh, web | 2 | named hosts |
| admin vpn | management | admin | 1 | named hosts |
Seven VLANs and a VPN overlay divide the network by trust. Three DMZ zones hold everything the internet can touch: an entry that ends TLS, a bridge that proxies a few narrow APIs inward, and a web zone for public apps. Clients, the phones and laptops, reach the internal reverse proxy and the internet, and admin devices a little more. Services hold everything internal. Management holds the hypervisors and the NAS, and admin devices reach it over the VPN. Storage has no gateway at all.
Between zones the default is deny, and every deny is logged. What is open is opened for a purpose, and almost always from named hosts rather than a whole zone. Nothing on the internet may start a connection in. Names and logins follow the same lines: each zone resolves only the names it needs, and a machine accepts logins only for its own zone.
a request for this page
A request for this page: visitor to cache (https), cache to relay (https, on a cache miss), relay to entry (the same stream, through the tunnel), entry to web (the site port only); the page returns the same way. visitor: asks for this page. cdn cache: answers from cache when it can, even when home is down. off-site relay: passes the encrypted stream on by name; holds no keys and no state. dmz entry: keeps the tunnel to the relay open (it dials out); tls ends here; proxies to the site. dmz web: a read-only container serves the built files. From inside, observability pushes hourly telemetry into the web zone; the web zone may send only logs + metrics and web back. Left and right arrows step a pulse through the path.
data
| hop | where | what it does |
|---|---|---|
| visitor | out there | asks for this page |
| cdn cache | out there | answers from cache when it can, even when home is down |
| off-site relay | off-site | passes the encrypted stream on by name; holds no keys and no state |
| dmz entry | home, dmz | keeps the tunnel to the relay open (it dials out); tls ends here; proxies to the site |
| dmz web | home, dmz | a read-only container serves the built files |
| observability | home, services | hourly: telemetry, pushed in with a key that can only write to one directory |
| dmz web → the lab | home | logs + metrics, web; nothing else |
A cache in front answers most requests, and keeps answering when home is down. On a miss the request goes to a small rented server off-site, which passes the encrypted stream on by name without holding any keys. It reaches home through a tunnel that the DMZ entry keeps open from the inside: the entry dials out, so the router never has to let anything in. TLS ends at the entry, which may reach the web zone only on the ports its apps serve. There a read-only container serves the built files.
The web zone can’t start much either. Into the lab it may only send its logs and metrics and fetch code through the internal proxy. The numbers on the front page travel the other way: once an hour the observability side pushes them in, with a key that can only write to one directory.
compute here, storage there
3 hypervisors and a NAS, one cluster. hypervisor a (general): 6 guests; hypervisor b (media + dmz): 4 guests; hypervisor c (always-on infra): 13 guests; the NAS runs none and keeps the bulk data and the replicas. 23 guests: 7 VMs, 16 LXCs. An LXC is the default; a VM when the guest needs its own kernel: everything in the DMZ, a device passed through, a host for many container stacks. Every member sits on the trunk (every VLAN) and on a storage network of its own with no gateway. Copies: 06:00, replicas, pulled by the nas; 06:30, a copy, pulled; 23:00–06:00, off-site copy, sent; sundays 03:00, cold copy, pulled. Left and right arrows step through the guests.
data
| machine | guest | zone | why | workload |
|---|---|---|---|---|
| hypervisor a | vm | dmz bridge | in the dmz: its own kernel | api bridge |
| hypervisor a | vm | services | hosts many container stacks | apps + files |
| hypervisor a | lxc | services | the default | apps + files |
| hypervisor a | lxc | services | the default | git + ci |
| hypervisor a | lxc | services | the default | git + ci |
| hypervisor a | lxc | services | the default | git + ci |
| hypervisor b | vm | dmz entry | in the dmz: its own kernel | public entry |
| hypervisor b | vm | dmz web | in the dmz: its own kernel | public apps (this site) |
| hypervisor b | vm | dmz web | in the dmz: its own kernel | public apps |
| hypervisor b | vm | services | a device passed through | media |
| hypervisor c | vm | services | hosts many container stacks | observability |
| hypervisor c | lxc | services | the default | access + identity |
| hypervisor c | lxc | services | the default | access + identity |
| hypervisor c | lxc | services | the default | access + identity |
| hypervisor c | lxc | services | the default | access + identity |
| hypervisor c | lxc | services | the default | access + identity |
| hypervisor c | lxc | services | the default | access + identity |
| hypervisor c | lxc | services | the default | access + identity |
| hypervisor c | lxc | services | the default | access + identity |
| hypervisor c | lxc | services | the default | apps + files |
| hypervisor c | lxc | services | the default | backups |
| hypervisor c | lxc | services | the default | backups |
| hypervisor c | lxc | services | the default | observability |
| nas | — | — | bulk storage; pool: bulk + replicas | no guests |
| 06:00 | copy | — | replicas, pulled by the nas | hypervisor a, hypervisor c → nas |
| 06:30 | copy | — | a copy, pulled | nas → hypervisor b |
| 23:00–06:00 | copy | — | off-site copy, sent | nas → off-site |
| sundays 03:00 | copy | — | cold copy, pulled | nas → cold copy device |
Each hypervisor has a job: one for general workloads, one for media and the DMZ, and one for the always-on infrastructure everything else leans on: access and identity, backups and observability. The NAS runs no guests. It is a member of the cluster but holds no vote, so the three hypervisors make the quorum on their own. Storage traffic has a link and a VLAN of its own. App data lives next to the apps, and copies move toward the NAS, pulled by the side that keeps them, before leaving for off-site and cold storage.
A guest is an LXC by default: light, quick to make, sharing the host’s kernel. It becomes a VM only when it needs a kernel of its own: everything in the DMZ, the guest with a device passed through to it, and the hosts that run many container stacks. That leaves seven VMs and sixteen LXCs.
operated, not just installed
- checks up
- 99.97%
- storage errors
- 0
- alert rules
- 26
The copy chain on a 24-hour day: daily 05:00, snapshots (kept 7–14 days (some up to 3 months)); daily 06:00, replicas (kept 30 days / 12 months / 2 years); nightly 23:00–06:00, off-site (kept rolling); sundays 03:00, cold copy (kept rolling). Each copy is pulled by the side that receives it, and every step checks in; a missed check-in raises an alert. Under the day, the alert coverage: 26 rules in 6 groups (silent pipelines, containers, storage health, memory, cpu, clock, ssh logins, tls expiry), an independent watchdog every 2 minutes, and critical alerts to a phone. A real schedule, drawn schematically.
data
| when | what | kept |
|---|---|---|
| daily 05:00 | snapshots of every important dataset | 7–14 days (some up to 3 months) |
| daily 06:00 | replicas pulled to a separate, read-only store | 30 days / 12 months / 2 years |
| nightly 23:00–06:00 | off-site copy of the newest replicas; stops itself at 06:00 and resumes the next night | rolling |
| sundays 03:00 | cold copy: a small device wakes itself on a timer, pulls, verifies the data, reports and powers off | rolling |
| always | 26 alert rules (as of 2026-10-10): silent pipelines (a machine stops sending metrics or logs, a stale data feed, a failed system unit); containers (CPU hog, restart loop, out of memory, log spam); storage health (disks, storage pools, SMART, temperatures); memory, cpu, clock (memory, CPU and clock); ssh logins (failed SSH logins); tls expiry (public TLS certificate expiry) | metrics, logs and check history 30 days |
| every 2 min | an independent watchdog checks the monitoring itself and alerts directly if it is down; critical alerts also reach my phone | — |
Copies follow a fixed daily rhythm: snapshots at five, read-only replicas pulled at six, an off-site copy that runs overnight and stops itself in the morning, and a weekly cold copy that wakes, verifies and powers back off. Copies at home are pulled by the side that keeps them, so a source can’t change or delete its own backups. Twenty-six alert rules cover the quieter failures, from a machine that stops reporting to a certificate about to expire, and a separate watchdog keeps an eye on the monitoring itself.