Get in touch →
01

Getting online

The internet enters through hardware I don't fully control. The ISP's own router terminates the WAN handoff and, by contract, can't be removed from the chain — so there are, a little absurdly, two routers stacked on top of each other before traffic reaches anything I actually manage: the ISP's box first, then mine.

Mine is a MikroTik hEX S — a router/firewall with no Wi-Fi radio at all, chosen deliberately as a routing appliance and nothing else. There's a small, honest story behind why it has no Wi-Fi: it wasn't entirely a plan. I ordered it without checking that it shipped Wi-Fi-less, missed the return window, and only afterward realized the accident had actually reinforced something worth keeping anyway — routing and firewalling shouldn't also be a Wi-Fi access point. So home wireless is served by a separate, older router from a previous ISP, kept around purely to be the Wi-Fi bridge into the network. Three boxes doing the work that in a datacenter would be one piece of gear, each there for a slightly different reason — contractual, deliberate, and accidental.

From the MikroTik, a managed switch fans out over trunk ports to every machine on the network — five of them, each carrying whatever VLANs it needs. What those machines are, and why their hardware doesn't match each other at all, is the next section.

The router stack — ISP router, MikroTik, and the old Wi-Fi router, side by side.
02

The machines

Nothing here was specced out in advance, and none of it was bought for the role it ended up playing. The cluster grew entirely out of hardware that was already sitting around — a repurposed laptop, a decommissioned office desktop, a tower with more headroom than the other two combined — and roles got assigned to what already existed, not the other way around. That history is visible in the specs below, and I'd rather show it than smooth it over.

Three Proxmox nodes carry almost everything:

NodeHardwareRAM / DiskWhat it carries
pve01Repurposed laptop (Asus GL502VS)32 GB · 500 GB SSD + 1 TB HDDIngress and day-to-day dev tooling
pve02Decommissioned office desktop (Dell OptiPlex 7050)16 GB · 256 GBObservability stack
pve03Dell T160 tower64 GB · 8 TB single diskData and the public-facing critical services

The sizing isn't symmetric, and that's on purpose even if the hardware itself wasn't chosen on purpose: pve02 runs metrics and dashboards, which is I/O-heavy but not RAM-hungry, so it got the smallest machine. pve03 holds actual state — databases, object storage, public traffic — so it got, by a wide margin, the most RAM and the most disk of the three. The laptop follows the same logic from the other end: nothing that needs real performance or holds anything that matters runs on pve01, precisely because the T160 exists for that instead — and because a laptop is not hardware anyone should trust with anything that matters: no ECC memory, no redundant power, a battery and hinges never built to run 24/7, and cooling designed for a desk, not a rack.

Machine names quietly follow Norse mythology (odin, brokkr, eitri, thor) — a bit of personality that costs nothing to keep — but the names actually used day-to-day, in configs, monitoring, and conversation, are the boring operational ones (pve01, pve02, pve03, dns01). That's deliberate: a name needs to be quick to say and instantly understood by anyone on the inside, and "pve03" does that in a way "thor" never will.

Out-of-band management is uneven too: only pve03 has a real hardware console reachable independently of its operating system. The other two don't — if one of them wedges badly enough, the fix involves walking over and pressing a button, not logging in remotely. Power-on behavior across the three is similarly inconsistent, for the same reason everything else here is inconsistent: three machines with three different histories, not one buying decision.

The three machines that actually run this, before any of them had a role assigned to them.
The three machines that actually run this, before any of them had a role assigned to them.
Proxmox cluster overview — the 3-node summary.

The smallest of these three isn't even a Proxmox node — a Raspberry Pi 3B+ sits alongside the cluster running the primary DNS for the whole network, a good fit for something that needs to stay on all the time and barely sips power while it does. That's next.

03

How names resolve

DNS runs as a primary/secondary pair — the primary is that Raspberry Pi from the previous section, the secondary lives as a VM on the largest node. Every record for the domains I actually own is authoritative here first; anything outside that gets recursed externally rather than trusted to whatever public resolver happens to be configured — a small, deliberate split: answer what's mine locally, ask the internet only for what isn't.

The two DNS servers stay in sync the standard way a primary/secondary pair should — zone-transfer notifications, authenticated so a rogue transfer can't inject records, over the same protocol clients use to query. Edits only ever happen on the primary; the secondary exists purely so resolution keeps working if the Pi is ever down, not as a second place to make changes. It's a small, textbook piece of infrastructure, and it's worth including for exactly that reason — not everything here needs to be exotic to be worth explaining well.

A query hitting the primary first, falling back to the secondary, and the split between local and recursed answers.
04

How the network is carved up

The network is split into eight segments, each one a trust boundary enforced at the edge router rather than a naming convention — what a machine can reach depends entirely on which segment it's in.

Illustrative addressing below — not the real ranges.

SegmentPurpose
HomePersonal devices — outside the infrastructure's own address space
InternalThe trusted application plane — most services live here
DataStorage and stateful services, deliberately hard to reach directly
CorpWork devices
CIBuild and automation workers, outbound access only
PublicInternet-facing — reachable only through one gateway
BackupReserved for a backup system that doesn't exist yet
MgmtDNS, hypervisor consoles, the admin-only control plane

The interesting part isn't the list, it's the reasoning behind it:

  • Data is isolated on purpose. Nothing reaches a database or the object store directly — only through explicit, named paths for the services that need them.
  • Public behaves like a classic DMZ, not a doorway. It's reachable from the internet, but it can only forward to a short, explicit list of backends — it is never a general path into the trusted internal network, even for things running on the same physical machine.
  • CI can reach out, but nothing trusts it to reach in. Build workers need outbound access to pull dependencies and push images; that's a completely different permission than being trusted to administer anything.

Two separate VPN networks sit alongside these segments for remote access — one for administration, one for development access — but they're routed overlays, not VLANs a peer ever directly joins, and the access model behind them belongs on /security, not here.

The eight segments and their purpose.
05

How traffic gets in

Three separate reverse proxies handle ingress, each one scoped to a different trust level rather than one proxy doing everything. This is probably the single decision on this page with the most "why" behind it, so it's worth explaining properly instead of just listing the three.

TierReachable fromWhat lives there
PublicThe open internet (allow-listed backends only)The only entrypoint reachable from the open internet. It forwards to a short, explicit allow-list of backends and nothing else.
InternalTrusted networks onlyFor tools meant to be reached from trusted networks only: dashboards, monitoring UIs, day-to-day dev utilities. This is the busiest of the three by a wide margin — most things people actually use daily live behind it.
ManagementThe administration VPN onlyThe infrastructure control plane. Hypervisor consoles, DNS administration, network gear, hardware-level access. Reachable only over the administration VPN, never from anywhere else.

The reasoning, as plainly as I can put it:

Compromising an app behind the internal proxy should never hand someone a path to a hypervisor console or a switch's admin panel.

That's the entire justification for the management proxy being its own instance, in its own network segment, gated behind its own VPN — not a convenience, a containment boundary. The rule of thumb I actually use when adding something new: if it can reboot a machine, touch the network configuration, or reach hardware directly, it goes behind management. Everything else is a question of audience — internal team, or the public internet. The same containment logic shows up again on /security, applied to people instead of proxies: it's the whole reason remote access is split into two separate VPNs rather than one with permission tiers.

Two edge cases worth calling out, because they're the kind of detail that actually explains the reasoning rather than just restating the rule:

  • A couple of internal tools are intentionally reachable through two of these proxies at once — same backend, reached differently depending on whether the caller is an external integration (a webhook, for instance) or a teammate on the trusted network. That duplication is deliberate, not leftover inconsistency.
  • The object storage service technically lives on the isolated data segment, but it's reached from outside that segment through the internal proxy rather than a direct firewall exception carved out just for it. The proxy path is the one sanctioned way in — not a shortcut around the isolation, the isolation working as intended.
The three proxies as trust tiers — who can reach each one.
06

Where the state lives

Everything with actual state — databases, object storage — is confined to the data segment described earlier, and access into it is deliberately narrow: only the specific hosts, proxies, and monitoring checks that need a path in have one, nothing broader.

Two pieces live there: RustFS, an S3-compatible object store, and two separate PostgreSQL instances — production and development kept fully apart rather than sharing one server with different databases on it. RustFS replaced MinIO after a license change and a community-edition UI that got noticeably worse; of the alternatives I looked at, RustFS had the best UI and is S3-compatible the same way MinIO was, so nothing built on top of it had to change. Direct database access is the exception, not the default; most things that need data go through an application layer in front of it, and the handful of things that connect directly (monitoring, a couple of trusted hosts) are named exceptions, not an open door.

The RustFS console — the object store behind the data segment.
07

What's actually running

Thirty-plus services sit on top of all of this, and the count itself isn't the interesting part — why each one is there is. The DNS pair, the edge router and switch, and the three reverse proxies are already covered above; what's below is everything else, grouped by what it's for rather than dumped into one long reference table.

One more detail worth a mention: internally, all of this is also tied together through a single dashboard — a self-hosted "homepage" style landing page that groups every internal link in one place. It's not something the public page needs to expose directly, but it's a good aside on how the day-to-day experience of running this actually works.

The internal dashboard that ties every link together day to day.

From here: /security covers how access into all of this is actually controlled, and /operations covers how it's kept running day to day. None of it is finished — /roadmap is the backlog, in order, of what's still open. And once something here changes, /log is where it stops being current state and becomes history.