Get in touch →
01

Getting online

The internet arrives through a box I don't control. My contract says the ISP's router stays in the chain, so traffic goes through two routers before it reaches anything I manage: theirs first, then mine.

Mine is a MikroTik hEX S running RouterOS. It does routing, NAT, firewalling and both WireGuard tunnels, and it has no Wi-Fi at all. That part wasn't a plan. I ordered it without checking, missed the return window, and only later realised the accident was a good rule: the box that enforces the firewall shouldn't also be an access point. Wi-Fi comes from an old router from a previous ISP, kept around only for that.

Behind the MikroTik, a TP-Link TL-SG608E managed switch carries tagged 802.1Q trunks to every machine, each one getting only the VLANs it needs. Three boxes doing what a datacenter would do with one, each there for a different reason: one by contract, one on purpose and one by accident.

From the WAN to the machines: the ISP router, the MikroTik that does the real work, and the switch that trunks VLANs to every host.
02

The machines

None of this was bought for the job it does. The cluster grew out of hardware I already had: a gaming laptop, a retired office desktop and a tower with more headroom than the other two together. Roles were assigned to what existed, and the specs below show it.

Three Proxmox nodes carry almost everything:

NodeHardwareRAM / DiskWhat it carries
pve01Repurposed laptop (Asus GL502VS, GTX 1070 Mobile)32 GB · 500 GB SSD + 1 TB HDDInternal and management proxies, Jenkins, Infisical and dev tools
pve02Retired office desktop (Dell OptiPlex 7050)16 GB · 256 GBObservability stack
pve03Dell PowerEdge T160 tower64 GB · 8 TB single diskDatabases, object storage and public services

The sizing is uneven on purpose. pve02 runs metrics and dashboards, which want disk I/O more than RAM, so it got the smallest box. pve03 holds the state, so it got the most memory and disk by far. pve01 runs only things that are easy to rebuild: a laptop has no ECC memory, no redundant power, and hinges that were never meant to run 24/7. Its GTX 1070 stays on the host and is shared with LXC containers through device bind-mounts, so an inference server can use it without a VFIO passthrough taking it away from everything else.

The machines have Norse names (odin, brokkr, eitri, thor) because it costs nothing and I like it. In configs, dashboards and alerts I use the boring ones (pve01, pve02, pve03, dns01), because at 3 a.m. "pve03" tells me what it is and "thor" doesn't.

Out-of-band management is uneven too. Only pve03 has a real one: an iDRAC, reachable only through the admin VPN, that can power the machine on even when the OS is dead. For the other two, if one hangs badly enough, the fix is walking over and pressing a button.

The three machines that run all of this, before any of them had a role.
The three machines that run all of this, before any of them had a role.
The Proxmox cluster summary for the three nodes.

There's a fourth machine that isn't a Proxmox node: a Raspberry Pi 3B+ running the primary DNS for the whole network. It's always on and uses almost no power, which is exactly what DNS needs. That's next.

03

How names resolve

I run two DNS servers because I once ran one. It went down, and a client noticed before I did. Both run Technitium: the primary on the Raspberry Pi, the secondary in a VM on pve03. They answer for albgott.com, and everything else goes to Quad9 over DNS-over-TLS, so lookups for the outside world aren't sent in plain text either.

The pair stays in sync the standard way: the primary sends a NOTIFY, the secondary pulls the zone with AXFR/IXFR, and every transfer is authenticated with TSIG so nobody can inject records. All edits happen on the primary. The secondary only exists so names keep resolving if the Pi dies. Textbook DNS, and I learned why it's in the textbook.

Clients ask dns01 first and fall back to dns02. Only names outside albgott.com leave the network, encrypted, to Quad9.
04

How the network is split

Eight VLANs, each a trust zone enforced on the MikroTik. What a machine can reach depends on which VLAN it's in and on nothing else. Traffic between VLANs is denied by default, and every exception is a rule built from RouterOS address lists: a source list, a destination list and a port.

SegmentPurpose
HomePersonal devices, outside the infrastructure's address space
InternalThe application plane, where most services live
DataDatabases and object storage, deliberately hard to reach
CorpWork devices
CIBuild agents, with outbound access only
PublicThe DMZ, reachable from the internet only through one proxy
BackupReserved for a backup system that doesn't exist yet
MgmtDNS, hypervisor consoles and the rest of the control plane

The list matters less than the reasons behind it:

  • Data is isolated on purpose. Nothing reaches a database or the object store unless a rule names that exact path, and only the few hosts that need it have one.
  • Public is a DMZ. It's reachable from the internet, but it can only forward to a short list of backends. It never becomes a path into Internal, even for services running on the same physical machine.
  • CI can reach out, but nothing trusts it. Build agents need to pull dependencies and push images. That's a very different permission from administering anything, so they only reach what a deployment needs and nothing in the management plane.

Two WireGuard networks sit next to these VLANs for remote access, one for administration and one for work. They're routed overlays, so a remote device never joins a VLAN; it gets an address the firewall can tell apart from local devices. How access works is on /security.

The eight segments and what each one is for.
05

How traffic gets in

Three Caddy reverse proxies handle incoming traffic, one per trust level. One proxy would be simpler, and that simplicity is exactly the problem.

TierReachable fromWhat lives there
PublicThe internet (allow-listed backends only)The only entry point from the internet. Ports 80 and 443 are forwarded to it, and it forwards to an explicit list of backends and nothing else.
InternalTrusted networks and the work VPNTools meant for trusted networks only: dashboards, monitoring UIs, registries, dev utilities. By far the busiest of the three, because it's where the things I use every day live.
ManagementThe admin VPN onlyThe infrastructure control plane: hypervisor consoles, network gear and hardware management. Reachable only from the admin VPN.

The rule behind it:

Breaking into an app behind the internal proxy should never give anyone a path to a hypervisor console or a switch.

That's why the management proxy is its own instance, in its own VLAN, behind its own VPN. My rule for anything new: if it can reboot a machine, change the network or touch hardware, it goes behind management. For everything else the only question is the audience: trusted networks or the internet. The same idea shows up on /security, applied to people instead of proxies: it's why remote access is two tunnels instead of one with permission levels.

Two edge cases, because they explain the rule better than the rule does:

  • A service can sit behind two proxies on purpose: external integrations such as webhooks come in through the public one, and people use the internal one. Same backend, two audiences, two sets of rules.
  • When a device at home opens a public name like albgott.com, the request would hit the WAN address and go nowhere. Hairpin NAT on the MikroTik sends it back to the public proxy, so the same URL works from inside and outside.
Who can reach each proxy, and what sits behind it.
06

Where the state lives

Everything with state (databases and object storage) sits on the Data VLAN, on pve03. Only the hosts, proxies and monitoring checks that need a path in have one.

There are two pieces. RustFS is an S3-compatible object store that replaced MinIO after its licence change and a community UI that got noticeably worse. Because it speaks the same S3 API, nothing built on top had to change. PostgreSQL runs as two separate instances, production and development, instead of one server with two databases, so a bad migration in dev can't touch prod. Both are scraped by postgres_exporter, with alerts for connection saturation, deadlocks, long idle-in-transaction sessions and a low cache-hit ratio.

None of it is backed up yet: it all lives on a single 8 TB disk. That's the first item on the roadmap.

The RustFS console, the object store on the Data VLAN.
07

What's running

38 services run on top of all this. DNS, the router, the switch and the three proxies are covered above. Below is everything else, grouped by what it's for, with one line on why each one is here.

Day to day, a self-hosted dashboard ties it all together: every internal link in one place, with live stats for each node.

The internal dashboard with every link in one place.

Next: /security covers how access to all of this is controlled, and /operations covers how it's kept running. None of it is finished; /roadmap lists what's still open, in order.