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