This repository is the single source of truth for my Kubernetes home lab. Everything from the operating system to application deployments is declared in code and reconciled through GitOps.
| OS | Talos Linux — immutable, API-driven, secure-by-default (lifecycle managed with topf) |
| CNI | Cilium — eBPF networking, kube-proxy replacement, L2 announcements, DSR, Maglev |
| GitOps | ArgoCD — ApplicationSets with a Git directory generator |
| Ingress | Envoy Gateway — three gateways (external, internal, direct) via the Gateway API |
| Storage | Rook Ceph (distributed block) + OpenEBS (local hostpath) |
| Database | CloudNativePG (PostgreSQL) + Dragonfly |
| Secrets | External Secrets (Infisical + CNPG ClusterSecretStores) + SOPS (age) |
| Auth | Kanidm — standalone OIDC / OAuth2 identity provider (+ LDAP) |
| Certs | cert-manager with Let's Encrypt DNS-01 |
| DNS | external-dns + Cloudflare (tunnel for public ingress) |
| Backups | Volsync — PVC backups to S3 |
| Monitoring | kube-prometheus-stack (Prometheus / Grafana / Alertmanager) + VictoriaMetrics + VictoriaLogs + Fluent Bit + Gatus + ntfy |
The environment is split across several clusters. The main workload cluster (pitower) depends on a set of single-node Raspberry Pi management clusters that host critical services, so a pitower outage can't take them down. All management clusters live on VLAN 20 (10.20.0.0/16), each in its own /24, with no VIP (the API endpoint is the node IP).
| Cluster | Role | Endpoint |
|---|---|---|
pitower |
Main workload cluster (6 nodes) | https://10.20.10.0:6443 (VIP) |
controller |
ArgoCD GitOps hub | 10.20.30.1 |
auth |
Kanidm + Vaultwarden | 10.20.40.1 |
pistack |
Supporting services | 10.20.20.1 |
| Node | Role | IP | CPU |
|---|---|---|---|
| worker-01 | Control Plane | 10.20.10.1 | AMD Ryzen |
| worker-02 | Control Plane | 10.20.10.2 | AMD Ryzen |
| worker-03 | Control Plane | 10.20.10.3 | AMD Ryzen |
| worker-04 | Worker | 10.20.10.4 | Intel (iGPU, dedicated=media-home taint) |
| worker-05 | Worker | 10.20.10.5 | Intel |
| worker-06 | Worker | 10.20.10.6 | Intel |
| Device | Purpose |
|---|---|
| Ubiquiti (UniFi) | Router + switching, VLANs, DNS interception |
| Ubiquiti U7-Pro & U6-Lite | Wireless APs |
| Cloudflare Tunnel | Public ingress (*.wibrow.dev → envoy-external) |
Cilium L2 announcements serve LoadBalancer IPs from the pool 10.20.10.128-255. The three Envoy gateways: envoy-external (10.20.10.239, Cloudflare tunnel), envoy-internal (10.20.10.238), envoy-direct (10.20.10.237, non-proxied public).
| Device | Purpose |
|---|---|
| Synology NAS | Media, backups, bulk data (NFS) |
| NVMe (worker-05/06) | Rook Ceph distributed block storage |
| Local SSD | Talos OS + OpenEBS local hostpath volumes |
home-ops/
├── .github/workflows/ # CI/CD pipelines
├── .justfiles/ # Just task runner recipes
├── ansible/ # Host provisioning
├── docker/ # Custom container images
├── docs/ # MkDocs documentation site
├── iot/ # ESPHome, Home Assistant, Button+ configs
├── kubernetes/
│ ├── apps/ # Application manifests — {cluster}/{category}/{app}
│ ├── argocd/ # ApplicationSets (per cluster)
│ ├── bootstrap/ # ArgoCD bootstrap
│ └── components/ # Reusable kustomize components (volsync, cnpg-db)
├── talos/ # Talos configs per cluster (topf-managed, SOPS-encrypted)
├── terraform/ # AWS, Cloudflare, etc.
├── scripts/ # Helper scripts
├── mkdocs.yml # Documentation site config
└── renovate.json5 # Dependency management
Cluster state is reconciled by ArgoCD. Each cluster has one ApplicationSet using a Git directory generator over kubernetes/apps/{cluster}/*/* — every {category}/{app} directory is discovered automatically, deployed as {cluster}-{category}-{app} into a namespace derived from its category.
Most apps are rendered from the app-template chart (bjw-s-labs) via a helm chart generator. The pitower cluster spans these categories:
kubernetes/apps/pitower/
├── ai/ # LiteLLM, Open WebUI, ToolHive, agent-sandbox, agentmemory
├── analytics/ # Rybbit
├── arc/ # GitHub Actions runners
├── banking/ # Financial tools
├── cert-manager/ # TLS certificate automation
├── cloudnative-pg/ # PostgreSQL operator + clusters
├── database/ # Dragonfly operator
├── dev/ # Dev/test workloads
├── home-automation/ # Home Assistant, Zigbee2MQTT, Mosquitto
├── kube-system/ # Cilium, CoreDNS, metrics-server
├── media/ # Jellyfin, Sonarr, Radarr, Prowlarr, qBittorrent, SABnzbd
├── monitoring/ # kube-prometheus-stack, VictoriaMetrics/Logs, Grafana, Gatus
├── networking/ # Envoy Gateway, external-dns, Cloudflare tunnel, Tailscale
├── openebs/ # Local hostpath provisioner
├── renovate/ # Renovate operator
├── rook-ceph/ # Distributed storage
├── second-brain/ # Knowledge tooling
├── security/ # Kanidm, External Secrets, RBAC
├── selfhosted/ # Homepage, Glance, Miniflux, Mealie, n8n, Mattermost, and more
├── system/ # Volsync, Reloader, KEDA, Headlamp, snapshot-controller, Spegel
└── workshop/ # Experiments
Adding a new application is as simple as creating a directory — ArgoCD discovers and deploys it automatically.
Shout out to the Home Operations community and Uptime Lab.
