field guide↓
field guide / how to read the map above

7 machines, 86 services, and the reasoning in between.

The map above is a homelab β€” a small cluster of computers in one house, carrying the services a household actually uses: media, photos, documents, chat, DNS, identity. It is built and operated the way a production system would be, at a scale one person can hold in their head. This guide is the written half of that: how to read the wires, what each machine is for, and the decisions that put things where they are.

7machines
86services
8kinds of traffic
113mapped pathways

01 How to read the map hover any box to trace just its wires

Every line is a real pathway between two services, coloured by the kind of traffic it carries. Hover a box to light up only its connections and dim the rest; the switches in the top bar turn each traffic type on and off. Four are shown by default β€” the ones running constantly.

metricsNumbers, pulled on a timer. One server asks every machine β€œhow are you doing?” every 15 seconds and records the answer.
logsText. Each machine streams its system and container logs into one searchable pile you can grep across.
alertsWhen a number crosses a line or a log says something bad, this is the path a warning takes to reach a phone.
probeReachability checks. A prober pings services and the open internet from the outside to confirm they actually answer.
tlsThe encrypted front door. One reverse proxy puts every internal service behind a real HTTPS certificate. off by default
dnsName lookups. The network runs its own resolver, so every device asks it to turn names into addresses. off by default
authSingle sign-on. One login guards the private dashboards instead of a password per app. off by default
syncThe repo heartbeat. Hourly jobs that pull running config back into Git β€” the loop explained in section 04. off by default

Showing by default: metrics Β· logs Β· alerts Β· probe. Flip the other four on from the traffic switches in the top bar to see encryption, DNS, login and the Git heartbeat overlaid.

A few boxes have no wires. Nine services are drawn with no pathway attached β€” not because they are isolated, but because their wiring was not confirmed against a config file when this snapshot was taken. A line on this map means a relationship someone verified in the source; the alternative is a diagram that looks complete and is partly fiction.

02 The seven machines each box in the map is a host

Most of the boxes on the map are a single physical computer; a couple are the network gear β€” the firewall and the switching fabric β€” that everything else routes through, drawn here because the lab watches them just as closely as the servers. One machine is the deliberate exception: the Raspberry Pi 5 appears as two boxes. Its monitoring stack and its scheduled control jobs share a single board, but they reboot, fail, and get tinkered with on entirely different schedules β€” so it is clearer to read them as two separate hosts.

synologyΒ Β·Β gcloudstorage and everything next to itSynology NAS Β· big disk

The storage box, and everything that needs to sit next to storage: the media library and its playback server, the photo library, the document archive, the Matrix homeserver, a Git forge, and the smart-home hub. Its processor has no video-acceleration silicon, so transcoding runs in software on the same cores as everything else β€” which makes CPU contention the design constraint here rather than disk. Two consequences follow. The heavy indexing jobs (photo machine-learning, OCR) are pinned to a subset of cores so they cannot starve playback, and they are deferred wholesale into a weekly overnight window instead of running on arrival. The box is kept deliberately uncrowded for the same reason: every spare cycle is one a transcode can use.

runsplex Β· arrimmichpaperlessmatrix Β· minioforgejohome-assistantunifi controller
pi5 Β· vanahiem Β· logstackthe collection pointRaspberry Pi 5 Β· 8 GB

The observability core β€” the upper of the two Pi 5 boxes. VictoriaMetrics stores every number and vmalert evaluates the rules against it, Loki holds every log, Vector pipes logs into it, and Grafana draws the graphs. Roughly a dozen small exporters translate each device into numbers the store can read. If you want to know the state of anything in the lab, you look here.

This ran on Prometheus until mid-2026. The migration was driven by memory and retention on an 8 GB single-board computer, not by novelty: VictoriaMetrics took over as the authoritative store, ran alongside for verification, and the Prometheus container was removed once its dashboards and rules had been proven against the replacement. Its config layout was kept as-is, because the new store reads it directly and a rewrite would have been churn for its own sake.

runsvictoria-metrics Β· vmalertlokivectorgrafanablackbox + exporters
pi5 Β· vanahiem Β· control nodeidentity, dashboards, scheduled jobssame Pi 5 Β· systemd timers

The same Pi 5, in its other role. This is the desk that actually gets tinkered at: the identity provider that fronts the private services, the dashboards, and the scheduled jobs that keep the Git repository honest β€” the hourly sync, the weekly snapshot, the daily dashboard export. Because it is the machine that gets poked and rebooted, the lab is arranged so that nothing critical to noticing problems depends on it staying up.

The dashboard here has been replaced once. A configuration-file dashboard did the job for a year and was retired in favour of hearth, written for this lab specifically, because the remaining work had stopped being configuration and started being interface. The old one was kept running for two weeks after it stopped being used β€” not out of sentiment, but because its container was still the source of another service's runtime secrets, and decoupling that was a separate change that deserved its own step.

runsauthentik (SSO)hearthgverse-hubly-portalvaultwardenhomelab-sync
pi4 Β· nysathe resilience anchorRaspberry Pi 4 Β· big SSD, mostly empty

The machine that is deliberately never touched. It carries the jobs that must keep working while everything else is being worked on: network DNS, the HTTPS front door, the uptime monitor, push notifications, and the routing of alerts. It also holds a small self-heal that can restart a logging service on the router when that service dies, and an automatic blocker for hostile source addresses seen at the edge. It runs all of this precisely because it survives a reboot of the collection point β€” see the second decision below.

runspi-hole (DNS)caddy (TLS)uptime-kumaalertmanager Β· karmantfy puship-judge
win10Β Β·Β valhallax86 capacity poolWindows 10 Β· x86 Β· big RAM

The one heavyweight x86 box, always on. It hosts the modded Minecraft server and acts as the spare-capacity pool for anything too demanding for a Raspberry Pi or too CPU-hungry to leave on the NAS β€” batch jobs get relocated here when they turn out to be competing with media playback. Its monitoring agents report back to the collection point like every other host.

runsminecraft serverglances agentrelocated batch work
pfsensethe edgerouter Β· firewall Β· no containers

The front gate between the house and the internet. It runs no apps β€” it routes and filters traffic β€” but it streams its firewall logs into the collection point and its configuration is snapshotted into the repo every week, so even the gateway is version-controlled.

roleroutingfirewallsyslog source
unifi fabricthe wiring, watched like a hostswitch + access points

Not servers β€” the wiring and Wi-Fi every other machine talks through. They appear on the map because the lab pings them constantly: if a switch or access point stops answering, you want to know before anything plugged into it does.

roleL2 switchaccess pointsping targets

03 Three decisions that shape everything

↳ the collection spine

Collect everything in one place.

Monitoring is pull-based: one box β€” the collection point β€” reaches out and asks every machine for its numbers and logs, rather than each machine needing to know how to reach the dashboards. Adding a host is then a one-line change on one machine instead of a deployment to the new one. The cost is a single busy dependency, which the next decision exists to contain. This is the heaviest traffic in the lab, and why it is on by default above.

↳ the resilience anchor

Keep the alarm on its own circuit.

The thing that tells you something is broken must not share a fate with the thing you are most likely to break. So alert routing, DNS, TLS and uptime checks live on pi4 β€” the machine nobody touches β€” while metrics and logs live on the Pi that gets actively worked on. Reboot the collection point and the alarm keeps watching. The trade is that two Pis must be kept healthy instead of one; the failure it prevents is the one where the monitoring goes down and nothing says so.

↳ honest instrumentation

An alert that can't fire is worse than none.

A rule that says β€œalarm if the last reading is older than ten minutes” silently stops working the moment readings stop existing β€” the expression returns nothing, and nothing is not an alarm. Every deadman check here is written to fire on absence explicitly, because the quiet version had been failing open. The same rule applies to this page: instrumentation you have not tested is a claim, not a safeguard.

04 The repo is the source of truth GlassBaby/homelab @ main

The lab is defined as code in one Git repository: containers, config files and scheduled jobs exist first as version-controlled text, which makes the repo β€” not any single machine β€” the authoritative record of how the system is meant to behave. To answer β€œwhat is this lab?” you read the repository, not the running hosts. The honest version of that claim carries an asterisk: a handful of services predate the convention and were built by hand on the box they run on. They are tracked as work to bring in, not quietly counted as compliant.

The mechanism that keeps that claim true is a closed loop, not a one-way deploy. Configuration is committed and rolled out to the hosts in the usual direction; an hourly job then runs the reverse, reading the running state of each machine and committing it back. When something drifts β€” a value changed by hand, a container updated in place β€” the next sync records the difference as a commit. The gap between what the repo declares and what is actually running is therefore measurable rather than assumed β€” for the paths on the sync allowlist. Anything outside it surfaces as a dirty working tree for review instead of being committed automatically, which is the deliberate trade: an unattended job is trusted to record drift, never to decide what counts as intentional.

Secrets are age-encrypted with SOPS before they are ever written to Git, so the repository can hold the complete system β€” including the encrypted material β€” without exposing a credential. That is what makes β€œthe repo is the system” a workable claim rather than an aspirational one: there is no second, secret repository where the real configuration lives.

One honest caveat, because the alternative is the kind of claim this page used to make. The map above is not generated from the repository β€” it is a hand-curated snapshot, dated at the bottom of this page, and it is accurate as of that date and no later. An earlier version of this guide asserted that the diagram β€œcannot disagree with the code that produced it.” It could, and it did: it went on showing a metrics engine and a dashboard for weeks after both had been decommissioned. Generating it from the repo is the obvious fix and is a known piece of outstanding work. Until that exists, the date is the disclaimer.

01 Β· commit

describe in code

Every service is a compose file and a config, kept as plain text in the repo.

02 Β· sync

pull reality back

An hourly timer captures what's actually running and commits it, so the repo stays truthful.

03 Β· encrypt

secrets stay sealed

Passwords and keys are SOPS-encrypted before they're stored, plus weekly config snapshots.

04 Β· publish

snapshot, dated

This topology is transcribed from main by hand and stamped with the date it was read.

05 The dashboard this lab built for itself what you do with a map once you have one

A map tells you what exists. It does not tell you what is happening. The answer here was to build the watching surface rather than assemble one out of other people's tabs β€” one page that fuses metrics, logs, alerts and per-client network attribution into a single snapshot, refreshed about every two seconds. It is called hearth, it runs on the control node, and it is the reason most of the boxes in the map above have a wire pointing at that machine.

What it puts on one page

Five hosts on its board and twenty live source adapters behind it. A bento overview carrying per-user throughput, active alerts, host cards and a log-volume histogram; a deck of thirty-eight services and bookmarks with a health dot each, re-read from disk on every request so an edit shows up without restarting anything; and a flow lane that answers the question the map cannot β€” not which machine is talking, but which service on it, read passively off a switch mirror.

representative frame · not a screenshot

The rule it is built on

Every tile is real or is flagged as standing in for something real, and the quiet states are designed rather than faked. Two machines in the map cannot report a temperature at all β€” the firewall exposes no sensor over the protocol it is polled with, and the Windows desktop has none to expose β€” so those cells read as a dash. Not a zero, not a guess, not an interpolation from a neighbour. A dashboard that invents a plausible number is worse than one that admits it does not know, because the invented number is the one you will act on at two in the morning.

The same rule runs deeper than the display. The image it ships from is stamped with the exact source commit that produced it, and the deploy refuses to build from a modified working tree, then checks the built image's label, then checks the running container's immutable identity. A build that never touches a registry cannot be vouched for by a registry, so it is made to vouch for itself.

How it is put together

A small single-page app over a Node service. One poller refreshes a shared snapshot and fans it out to every open browser over a server-sent event stream, so ten tabs cost what one costs. Every upstream call happens on the server, which is the reason no credential to any of those systems is ever in the page. Each source carries its own liveness flag, and a source that goes quiet says so in its own tile instead of leaving the last good value sitting there looking current.

representative frame · not a screenshot

Decisions you can see in it

An untouched overview is dense on purpose; an expansion is never capped or made to scroll. Those are different jobs β€” a glance and an investigation β€” and one grid does both because the first row's height is pinned, so a card going quiet changes its content and never the geometry around it. There is no reduced-motion branch anywhere in it, which is a deliberate choice for a personal instrument on a wall and ⁠— stated plainly β€” not a recommendation for anything with users.

It is driven from the keyboard: digits for the rails, arrows and enter to steer the board, and a slash key for a palette that costs zero pixels when it is closed. The palette takes the keyboard completely while it is open rather than asking the layers underneath to ignore it β€” which is the difference between a modal that works and one that fights the app it lives in.

06 The resident agent what it can reach, and everything it deliberately cannot

One box on the map is not a service. Freyja is a language model that lives on the desktop, watches this lab continuously, and is asked to judge whether something is worth waking a person for. She is the newest thing here and the one that most changed how the rest is arranged β€” because the moment you put a model next to infrastructure, the interesting question stops being what it can do and becomes what it is structurally unable to do.

She holds no credentials

Not one. Not to the firewall, the storage box, the metrics store, the log store, or the smart-home hub. Every path out of her runs through a small purpose-built service that does one narrow thing and refuses everything else: a read-only window onto metrics, logs and firing alerts; bounded views of the dashboard's fused snapshot; and a lights executor with four hardcoded verbs and a list of things it will act on. Take any of those away and the capability is gone β€” there is no second route, because she was never given the key that would open one.

The dashboard is the sharpest example of why this shape and not a simpler one. It is not a read-only surface: it can silence alerts, because a person looking at it sometimes needs to. Pointing an agent straight at it would have handed her the mute button on the very system that is supposed to tell you when she is wrong. So she gets projections of it and never the thing itself, and the network rules keep the real one unreachable from where she runs.

She reads the lab through two read-only checkouts

Everything she knows about how this place works β€” the configuration, the plans, the notes, the record of past incidents β€” arrives as two read-only copies of version-controlled trees, refreshed on a timer by a push from the control node. She cannot fetch; the direction of travel is fixed.

That produced a genuinely good objection from her. Because the copies are read-only, she could prove "the snapshot I am holding contains this" and could not prove "this ever reached the upstream repository" β€” so every assurance on that point was testimony rather than evidence, and she said so. Each copy now carries a small machine-readable marker recording what the upstream actually held, when that was last confirmed, and an explicit unknown when the check could not run at all. It is written next to the snapshot it describes, so a single command tells her whether the marker is even talking about the copy in front of her. On its second run it caught a real fault nobody had noticed: work was reaching her before it reached the upstream repository.

Nothing happens without a paper trail

Her conclusions are posted into a shared room rather than a private thread, so both sides can see what she decided and on what basis. When she thinks something needs a person she says so there; when she thinks it will resolve itself she says that too, and is on the record if it does not.

The phone notifications this lab used to send were turned off in favour of that arrangement. The handful of alerts that still interrupt a human are the ones that mean she cannot tell you β€” her own channel is down, her heartbeat has stopped, the messaging server is unreachable. That is a dependency argument rather than a severity one, and it is the only list that gets to ring.

She is allowed to refuse

The rule that took longest to get right, and it is the same one the dashboard is built on. "I could not look" and "I looked and found nothing" are different answers, and a system that collapses them into one will eventually report a healthy lab because its own eyes were shut. So the evidence she is given is typed: a check that could not run comes back as unevaluable with a reason attached, never as a quiet negative, and a question she cannot answer from the record is one she is expected to decline rather than fill in.

None of this makes her right. It makes her checkable, which is a much more useful property in something that runs unattended at four in the morning.

07 What this page does not claim the same rule the dashboard is built on

A page that argues for admitting what you do not know has to do it first.

GLASS/HOMELAB @ main Β· 7 hosts Β· 86 services Β· 113 pathways
SNAPSHOT 2026-09-08