Every self-hosted fleet I’ve seen past its first year has the same shape, whether it started as one Raspberry Pi or three racked servers. A pile of things run on one box, or one flat network, because that’s how it started and nobody ever had a reason to stop. The reason usually shows up eventually, and it shows up as an incident, not as a planning meeting.
I want to make the case for doing this on purpose, before that happens. Not because segmentation is best practice in the abstract (I don’t find “best practice” a very persuasive argument for anything on its own), but because the alternative has a specific, predictable failure mode, and once you’ve watched it happen you stop being able to unsee it.
The failure mode
Here’s what “organic growth” produces, in practice. You start with one virtual machine because setting up a second one felt like overkill for two services. Eighteen months later that VM is running a media library, a notes app your partner uses daily, a link shortener, three experiments you half-abandoned, and a reverse proxy sitting in front of all of it. None of these things have anything to do with each other. They share fate anyway, because they share a kernel.
Something eventually goes wrong with one of them. A memory leak, a bad update, a dependency that starts misbehaving under load; it genuinely doesn’t matter which. What matters is the failure doesn’t stay contained to the thing that broke. The out-of-memory killer doesn’t know which of your services you’d prefer to lose. It picks based on its own heuristics, and sometimes it’s wrong, and by the time it’s wrong the box is already in a bad enough state that “restart the one service” isn’t an option anymore. I’ve watched this exact pattern play out more than once, on more than one setup. A background job that had no business being critical takes the whole host down with it, and everything else on that box, things that were working fine thirty seconds earlier, goes down as collateral.
The uncomfortable part isn’t that it happened. Things break; that’s not news. The uncomfortable part is that it was avoidable in a way that had nothing to do with fixing the bug. The notes app your partner uses had no causal relationship to the experiment that leaked memory. They only failed together because someone (me, more than once) decided a VM boundary wasn’t worth the setup time. That decision is the actual root cause. The leak is just the trigger.
Trust domains are the unit you’re actually missing
The fix isn’t “run fewer services” or “audit your code more carefully,” though both are fine ideas on their own terms. The fix is picking the right unit of design, and the unit most homelabs are missing is the trust domain — a boundary drawn around things that share risk tolerance and consequence, not around things that happen to share a network cable.
I think about it in three rough buckets, though your mileage on the exact split will vary:
Personal experiments. The stuff you’re trying out, breaking on purpose, running one version behind because you haven’t gotten around to the upgrade. If this goes down, you notice, you shrug, you fix it when you feel like it. Nobody else is affected and nobody else knows it happened.
Things other people depend on. A shared calendar, a family photo library, a service a friend actually uses daily. The failure mode here isn’t “annoying,” it’s “someone messages you asking why the thing is broken.” The uptime bar is real even if it’s informal, and the blast radius of a mistake includes someone else’s evening.
Anything internet-facing. Different category entirely, because the threat model changes completely the moment something is reachable from outside your network. Uptime stops being the main question. Exposure is. A misconfigured service in this bucket isn’t a shrug, it’s a scan result showing up in someone else’s recon.
These three buckets have different uptime expectations, different threat models, and different acceptable blast radii, which is the part that matters most for design. A crash in the first bucket should cost you nothing but your own time. A crash in the third bucket can cost you a compromise that spreads to whatever else was reachable from the same place. Treating all three as one undifferentiated “my stuff” is the organic-growth trap restated as an architecture diagram.
What a boundary looks like at this scale
“It’s all behind my firewall” is not segmentation. It’s perimeter defense, and perimeter defense answers a different question than the one you’re asking. The firewall keeps things out. It does nothing about a compromised or crashed service reaching sideways into something that happens to share its network. Internal lateral movement doesn’t care what your router’s WAN rules say.
The mechanisms that create boundaries are unglamorous and, at homelab scale, close to free:
Separate virtual machines or VLANs per trust domain, so a compromise or crash in one domain has to cross a real boundary to touch another instead of a process table. A VM costs you some RAM and a little setup time. It doesn’t cost you money, and it doesn’t cost you meaningfully more operational overhead than you already have, because you’re already managing services; managing them in three groups instead of one flat pile isn’t a new skill.
One SSO or forward-auth gateway per domain, not one gateway ruling every subdomain you own. This one surprises people, because consolidating auth feels like the security-conscious move. It is, right up until that single gateway becomes a single point of failure that takes every gated service down with it on a bad day. I mean that specifically, not hypothetically: an auth layer that gates a dozen unrelated apps means a caching problem in that layer makes every one of them look broken at once, when none of them actually is. A gateway per trust domain means a lab-domain auth problem stays a lab-domain problem.
DNS and reverse-proxy separation, so the routing layer itself doesn’t quietly re-merge domains you meant to keep apart. If your internet-facing services and your personal experiments resolve through the same proxy config, sharing the same certificate chain and the same access logs, you’ve drawn the boundary on a diagram and then routed around it in practice.
None of this requires new hardware. It requires deciding, in advance, which things are allowed to share fate, and then refusing to let convenience quietly merge them back together six months later, which is the part people fail at most. The technical boundary is the easy half.
You never get to start over
The honest problem with all of this is that nobody redesigns a fleet from a blank slate. You’re not going to get a weekend where everything’s down and you rebuild clean — or if you try to schedule one, it’ll slip, because it always does when the thing you’re rebuilding is also the thing serving your notes app to your partner right now.
So the sequencing has to work against a live system, and the order matters more than people expect. Move stateless services first — the things with no database, no persistent state, nothing that breaks if you spin up a fresh instance in the new domain and just point traffic at it once it’s healthy. That’s your low-risk practice run, and it teaches you where your new boundary’s rough edges are before you touch anything that would hurt to lose.
Keep an edit-here-deploy-there separation as you go, rather than editing services live inside whichever domain they’re migrating through. It’s tempting to make quick fixes directly on the box mid-migration because it’s right there and the change is small. Don’t. The moment you start editing in place, you’ve lost track of which version is authoritative, and untangling that later costs more than the shortcut saved.
And accept, honestly, that the transition is long. Stateful services, anything with a database, anything with accumulated history you can’t regenerate, move slower, because you have to get the data migration right and you generally only get one clean cutover before someone notices the service was briefly in two places at once. A fleet that grew organically over two or three years does not get re-architected in a weekend. It gets re-architected over months, one domain boundary at a time, with the old flat topology and the new segmented one coexisting uncomfortably in the middle the whole way through.
The economics favor you
Here’s the part that should make this an easy call rather than a hard one: at homelab scale, none of this costs real money. A VM is free once you already have the hypervisor. A VLAN is a config change on a switch you already own. You are not buying a second firewall appliance or a compliance consultant. The entire cost of trust-domain segmentation, at this scale, is discipline — the willingness to say “this new service goes in the shared-with-others domain, which means it gets the stricter uptime bar and the stricter auth gateway, even though the lab domain would’ve been three minutes faster to set up tonight.”
That’s a real cost. I’m not going to pretend it isn’t, because the three-minutes-faster option wins on a lot of nights, and every homelab I’ve seen with a flat topology got that way through a hundred individually reasonable three-minute decisions. But weigh it against the payoff. A mistake in your lab domain, the place where you’re supposed to be free to break things, can no longer reach the domain where your family’s calendar lives, or the domain that’s exposed to the internet. That’s the whole point. Not that nothing ever breaks. Things will keep breaking; that’s what happens when you keep building. It’s that when something breaks, it breaks alone.
The question worth asking about any new service, before you spin it up, isn’t “where’s the easiest place to put this.” It’s “what does this share fate with if I put it there, and am I willing to lose that too.”