s3lcsum

The one project

Homelab

Home infrastructure as code โ€” the same rigour I'd expect in production, on hardware that lives in my flat.

  • 23 Compose stacks
  • 13 OpenTofu modules
  • 52 Containers
  • 1 Repo of record

Everything I run at home lives in one repository. Not a folder of compose files I edit over SSH and forget about โ€” modules, pinned versions, runbooks, and a review step. If it isn’t in git, it isn’t in the lab.

The rule I hold myself to is that I should be able to lose any box and rebuild it from the repo. That’s why the router config, the identity provider, the dashboards and the alert rules are all code. The interesting part isn’t the list of services; it’s that almost nothing in it was set up by clicking.

It’s also where I get to make the mistakes I don’t want to make at work. When I moved monitoring to config-as-code, my first alert rule fed a == 0 expression into a > 0 threshold โ€” so a dead service produced a 0 that never crossed the line and the rule sat there looking healthy. I found it by stopping Sonarr and watching the probe read zero for several minutes while nothing fired. Both rules now threshold the raw metric, and I verified the full cycle: stop the service, alert fires, start it, alert resolves.

The network is planned, not grown. One flat *.dominiksiejak.pl domain for everything, AdGuard as the DNS sinkhole, and an addressing scheme that means something: .0โ€“.9 for network gear, .10โ€“.99 static, .100โ€“.199 the DHCP pool, .200+ lab interfaces. NetBox holds the inventory, RouterOS holds DHCP and NTP, and both agree because both are generated.

The repo has receipts, too. Authentik was swapped for ZITADEL and then swapped back after two weeks of fighting it. Portainer’s GitOps pull got ripped out in favour of plain terraform apply because waiting for a sync was worse than running one command. Nginx Proxy Manager became Traefik. Each of those is a changelog entry saying what broke and why it came back โ€” which is the part most homelab repos skip.

IHow it's put together

Edge

Traefik is the single entry point for all public and internal traffic, reachable directly and from behind WireGuard. The firewall drops unsolicited traffic to the public IP, a few hosts are published through Cloudflare so floods never reach the router, and Tailscale runs on every instance as a failover VPN in case WireGuard goes down.

  • Traefik
  • CrowdSec
  • Cloudflare
  • WireGuard
  • Tailscale

Identity

Authentik is the single sign-on for the whole lab โ€” OAuth, SAML and LDAP, down to the NAS logins.

  • Authentik
  • OIDC
  • LDAP

Compute

Proxmox runs the LXC containers and VMs, declared in OpenTofu instead of clicked together in the web UI.

  • Proxmox VE
  • LXC
  • OpenTofu

Network

RouterOS is code as well โ€” firewall rules and the WireGuard peers I use to get back in from outside.

  • MikroTik
  • RouterOS
  • WireGuard

Inventory

NetBox is the source of truth for devices, prefixes and VLANs, and it is managed as code like everything else.

  • NetBox
  • IPAM
  • DCIM

Secrets

Vault hands out the credentials stacks need at runtime, so nothing sensitive sits in git or in Terraform state.

  • Vault
  • Vaultwarden

Observability

Grafana and VictoriaMetrics are provisioned from config, Blackbox probes 25+ endpoints, and alerts route to Telegram and email.

  • Grafana
  • VictoriaMetrics
  • Blackbox
  • Gatus

Home

Home Assistant with Zigbee2MQTT and Mosquitto, plus n8n for the automations that stitch services together.

  • Home Assistant
  • Zigbee2MQTT
  • n8n

Media

Jellyfin and the *arr stack, with the downloaders routed through a VPN gateway container.

  • Jellyfin
  • Sonarr
  • Radarr
  • Gluetun

IIThe metal

Main server
FIREBAT T8 Pro Plus mini PC โ€” Intel N100, 16GB DDR5, 512GB SSD, running Proxmox VE
Secondary
Dell PowerEdge R610 โ€” the experiment box, mostly powered down
Repurposed
Second MacBook Pro (Intel) โ€” retired from daily duty, now extra compute
Storage
Synology DS220+, 2 ร— 4TB โ€” primary storage and backups
Router
MikroTik hAP ac3 on RouterOS, handling DHCP and NTP
Wireless
Ubiquiti U7 Lite + TP-Link Deco M4R ร—3 mesh
Uplink
1 Gb/s symmetric fibre, public IP

IIIHow it's run

One way to change things

Every OpenTofu module carries the same Makefile โ€” init, plan, apply, validate, fmt โ€” and remote state lives in a GCS bucket.

Nothing on :latest

Images and providers are pinned to explicit versions and bumped by Renovate, so an upgrade arrives as a reviewable diff instead of a surprise at 2am.

Guardrails before merge

Pre-commit hooks run alongside consistency and security-invariant checks, so the obvious mistakes never reach the host.

Restore from the repo

Stacks ship env templates and seed config, so a service comes back from git rather than from memory.

Hermes does the chores

OpenCode and Hermes handle the day-to-day. Hermes watches for new versions across the lab, and when an upgrade has no breaking changes it commits and applies it to the repo itself โ€” monitoring keeps an eye on the rollout, and a failed check triggers an automatic rollback.

Secrets stay out of git

Stacks commit only .env.example skeletons; the real values are injected at runtime by Vault โ€” including the OAuth client secrets Authentik depends on, so there is nothing sensitive sitting in state either.

The house answers messages

Family can message the Facebook page and get Home Assistant to act: n8n verifies Meta's webhook, maps the sender to a person with their own conversation thread, and calls the HA conversation API. A Cloudflare rule exception lets Meta's servers through the geo-block so it works at all.

IVNext on the list

  • Move backups off Proxmox and onto the NAS
  • Migrate cloud drives onto the NAS
  • NUT/UPS integration so the lab survives a power cut
  • Isolate IoT devices onto their own subnet
  • Self-hosted LLM on one of the Proxmox boxes