Homelab GitOps
a two-node Kubernetes cluster, run like production
K3s across a rented VPS and a machine at home, Argo CD app-of-apps, one Helm chart per app, Jenkins bumping tags, sealed secrets. It deploys everything else on this page.
- Everything, design, charts, pipelines, operations
- K3s · Argo CD · Helm · Jenkins · Traefik · WireGuard · Sealed Secrets
- In production, at home
The problem
A game server, a web panel, a private registry, a CI, an embeddings service, a couple of websites: side projects pile up, and each one deployed by hand is one more thing that breaks silently. I wanted a single way to put anything in production: declarative, reviewable, reversible with a git revert. And I wanted it on hardware I pay for and understand.
What I built
A K3s cluster on two nodes: a rented VPS that carries the control plane (cordoned, it schedules nothing) and a machine at home that runs every workload, linked by WireGuard. Traffic enters through Cloudflare DNS, hits the VPS on ports 80 and 443, is forwarded through the tunnel to Traefik on the home node, then Ingress, Service, pod. The game servers stay native on the VPS, outside Kubernetes on purpose.
One git repository is the source of truth. Argo CD in app-of-apps mode watches it with prune and self-heal on; every application is a small Helm chart copied from a commented template, never a third-party chart with drifting tags. Jenkins runs in the cluster, builds images in pods, pushes to a private registry, then commits the new tag into the infra repository: that commit is the deployment. Secrets are sealed with a master key kept offline; cert-manager issues TLS; KEDA scales rarely-used services to zero and wakes them on the first request; MinIO holds object storage and nightly pg_dump backups. Ten applications are active, thirteen more are archived with their charts and dumps kept.
What I learned
That self-heal is a commitment: nothing can be paused from the cluster, so even "retiring" an app became a repository convention. That host-network ingress, ExternalName services and Argo's resource exclusions each have one silent failure mode, and that writing them down as numbered traps is the only defence. And one honest negative result: I removed Prometheus, Grafana and Loki to give 1.6 GB back to a Minecraft server, and measured that it changed nothing about the game's tick. The bottleneck was elsewhere.
“If it is not in git, it does not exist on the cluster, including the things I would rather have paused by hand.”AltiTrak