homelab
How my Kubernetes homelab is built
Ten machines, one Kubernetes (k3s) cluster and about thirty self-hosted apps, run the way I’d run production. Everything, from the OS install to each app’s deployment, is declared in git.
From the apps down to the hardware
Apps & AIOps
About thirty apps, each deployed from git and behind single sign-on. The AI work runs on local hardware.
AIOps
- Request to the LLM
- Wake proxy in the cluster
- Wake-on-LAN
- Ollama answers
When idle, the workstation suspends itself: GPU capacity with near-zero idle power.
- Alertmanager
- LLM groups and summarises
- chat
Related alerts are grouped and summarised before they reach my phone: one incident, one message.
- Alert or failed Argo CD sync
- Matching Loki logs
- LLM drafts a first triage
Alerts, failed syncs and degraded apps each come with a first read of what went wrong, drafted from the logs around them.
- Today’s Loki errors
- Compared with yesterday’s
- Only new patterns posted
Errors that were already there yesterday stay quiet; only new patterns reach the chat.
- A week of alert history
- LLM reads it
- Rules to tune or drop
The model reads the week’s alert history from VictoriaMetrics and suggests which rules to tune or drop.
A local model reviews Renovate’s dependency PRs and comments on them.
An AI agent workspace running in the cluster, with its own Vault access.
Self-hosted apps
- Immich
- Nextcloud
- Paperless-ngx
- Vaultwarden
- Home Assistant
- Homepage
- Servarr
- Stirling-PDF
- IT-Tools
- draw.io
My Kubernetes operator for freqtrade trading bots: bots, strategies and backtests are custom resources. Paper trading only.
Delivery & lifecycle
Every layer is declared in git, so changing the cluster means making a commit.
Declared in git, layer by layer
- OS installFedora CoreOS · Butane / Ignition
An immutable OS, installed from a config file.
- Host setupAnsible
Network and storage roles, run against every machine.
- Kubernetes versionSystem Upgrade Controller
Upgrade plans: the version lives in a file.
- Platform & appsArgo CD · app-of-apps
About thirty apps, split into infrastructure and app projects.
- SecretsVault
Only references in git; the values are synced from Vault.
Upgrades at every level
- New CoreOS release
- Node updates itself
- Waits its turn: one node at a time
- Drained, rebooted, back in service
- Version bumped in a plan
- Commit
- Nodes upgrade themselves
- Renovate opens a PR
- Local LLM reviews it
- Merge
- Argo CD rolls it out
Two pipelines
- Push
- Gitea Actions builds the image
- Argo CD deploys from git
Gitea and its CI runners, amd64 and arm64, run in the cluster itself.
- dev: lint, type-check, build
- staging: served by Apache, smoke-tested
- main: deployed, smoke-tested again
The page you’re reading shipped through it, on the cluster’s own runners.
Edge & security
Every app has a single name. From the internet it leads through Cloudflare and a web application firewall; at home, the same name leads straight to a private gateway. No inbound port is open.
One name, two paths
- Cloudflare DNS
Public records, created from each app’s manifest. Only public apps get one.
- Cloudflare Tunnel
Outbound-only: no open port and no exposed home IP.
- BunkerWeb
Web application firewall and public gateway, with rules and rate limits per app.
- Technitium
Answers for the same zone at home, from three replicas.
- MetalLB
Gives the private gateway its address on the home network.
- NGINX Gateway Fabric
The private gateway: traffic from home never leaves the house.
Both paths end in the same place
- Authentik
Sign-in on both paths: OIDC where the app supports it, a proxy where it doesn’t. MFA required.
- App
Private-only apps have no public record at all.
Two external-dns instances write both views from the same manifests, so no DNS record is edited by hand.
Around the paths
Default-deny across the cluster; each app opens only the flows it needs. From the home network, the nodes answer only SSH, the Kubernetes API and ping.
Traffic between nodes is encrypted, even inside the home network.
Three-node raft cluster with auto-unseal. Secrets are synced into apps, never stored in git.
TLS certificates issued and renewed automatically, wildcards included.
Every namespace has a security level: baseline, privileged only where host access is needed.
The built-in admin account is disabled: access goes through SSO with MFA.
Hardware, power & data
Ten machines serve the cluster: seven run k3s, three support it.
The machines
Keeps storage, the control plane and the network up through outages. Alerts over SNMP when on battery.
No inbound port forwarded.
Fedora CoreOS. Three form a highly available control plane; one is a worker.
Fedora CoreOS too. Cheap extra capacity; heavy workloads are kept off them.
Holds backups outside the cluster they protect.
Serves local LLMs. Sleeps when idle, woken on demand.
Provisioned and firewalled as code, linked over WireGuard.
A second copy of the NAS backups, outside the house.
On the UPS: the NAS, 1 control-plane node, the router and the switch.
Where data goes
- Longhorn volumes
Replicated across nodes, with a snapshot every hour.
- Database dumps
Daily, consistent at the application level. etcd snapshots are copied off the nodes too.
- NAS
Nightly backups of every volume.
- Off-site backup
The last line if the house itself is the problem.
Restores are rehearsed on a scratch database, not just planned.
Watching all of it
- Metrics
- VictoriaMetrics, Prometheus
- Logs
- Loki, Promtail
- Traces
- Tempo
- Probes
- Blackbox exporter
- Hardware
- SNMP
- Dashboards
- Grafana
- Alerts
- Alertmanager → chat
SNMP covers the UPS and the NAS, so a power cut raises an alert like any other failure.