Skip to content
/ homelab Public

A K8s homelab on public VPS. Built to learn from — or fork and make your own.

License

Notifications You must be signed in to change notification settings

tograu/homelab

Repository files navigation

Homelab

A personal Kubernetes homelab built with Ansible (IaC) and Argo CD (GitOps), running on VPS infrastructure.

It's designed to be rebuilt, not to never fail.

Why

I wanted to understand how a Kubernetes platform actually fits together. Every component here was picked, configured and wired up precisely so I know what it does and why it's there.

It runs on public VPS because of no upfront hardware cost, fixed monthly pricing and availability from anywhere. But nothing here is VPS-specific: the same setup works on Proxmox VMs, bare metal or any Debian machines with SSH access.

What's Running

Component Status
⚙️ CI Linting — YAML, Ansible
🛡️ CI Security — Gitleaks, Kubescape
🖥️ 3 public VPS — 1 control plane, 2 workers (connected via vLAN)
🔧 Ansible — Idempotent K3s cluster bootstrapping
🧱 nftables — Adjustable IP allowlisting
🔄 nftables auto-update — Automatic re-resolve on dynamic IP change 🔜
🚀 ArgoCD — App-of-Apps GitOps from this repo
🌐 Traefik + Gateway API — HTTPS-only, IP-allowlisted
🔒 cert-manager — Let's Encrypt wildcard TLS via Cloudflare DNS-01
📦 Image Registry — Pull-through cache + private images 🔜
📊 Monitoring — VictoriaMetrics + Grafana + email alerting
📝 Logging — VictoriaLogs + Vector
🔍 Visibility — Headlamp
💾 Backup — For all stateful workloads 🔜

Workloads

Workloads live in a separate, private repository, deployed as an independent ArgoCD App-of-Apps. The Ansible argocd role is already prepared for this (argocd_workloads_enabled).

Ideas

What's still missing for the allmighty production ready homelab (as time permits):

  • 🏗️ High availability — Multi-CP with embedded etcd, replicated deployments, topology spread constraints
  • 💾 Distributed storage — Replicated persistent volumes, replacing K3s local-path
  • 🐝 eBPF-based CNI — Replace Flannel with Cilium for advanced networking
  • 🛡️ Policy enforcement — Admission control (resource quotas, label standards, image policies)
  • 🔐 Secret management — Move from Ansible Vault to a proper solution
  • 📡 OpenTelemetry — Unified collection for metrics and logs
  • 🔭 Tracing / APM — Distributed tracing for workloads
  • 🪪 Identity provider — SSO for all cluster apps, OIDC-based RBAC
  • 🔑 VPN — Secure tunnel to the cluster, complementing IP allowlisting
  • 🛒 Self-services — Internal platform portal for deploying services on demand
  • 📖 Docs — Write up decisions, guides, and lessons learned

Security Notice

Exposing a Kubernetes cluster to the public internet is an inherently risky operation. This setup includes IP-based access control via nftables. If no allowlist is configured, Ansible will prompt with an interactive warning before proceeding. Make sure you understand the implications before deploying, and only expose services intentionally.

SSH is not filtered by nftables. The bootstrap playbook enforces key-only authentication by disabling password login via sshd configuration, ensuring sufficient security.

If your IP allowlist uses a dynamic hostname (e.g. DDNS), re-run make cluster after an IP change to update the nftables rules (dynamic adjustment is planned).

Running It Yourself

First and foremost, it's a personal setup, but I've tried to balance two goals:

  • Transparency: Showing how things work in my lab
  • Reusability: Providing enough context for others to adapt it

Prerequisites

Infrastructure

  • At least 2 VMs with Debian 13 and root SSH access (for initial bootstrap)
  • 1 control plane, 1+ workers — connected via private network (public IPs optional but currently intended)
  • SSH key at ~/.ssh/id_ed25519 (key-only auth, no password)
  • A domain managed via Cloudflare (for TLS certs using DNS-01 challenges)

Deploymentmake bootstrap, make setup

  • Ansible + collections: ansible.posix, community.general, kubernetes.core

Local checksmake lint, make scan

  • yamllint, ansible-lint, kustomize, Helm, gitleaks, kubescape

Configuration

Copy the .example files and fill in your values:

  • ansible/inventory/hosts.ini
  • ansible/inventory/group_vars/k3s.yml
  • ansible/inventory/group_vars/controlplane/vault.yml
  • ansible/roles/argocd/defaults/main.yml

Replace the domain in the HTTPRoute and certificate manifests with your own:

  • argocd/kustomize/traefik/certificate.yaml — wildcard cert scope
  • argocd/manifests/argocd/httproute.yaml — ArgoCD UI
  • argocd/kustomize/monitoring/httproute.yaml — Grafana UI
  • argocd/kustomize/headlamp/httproute.yaml — Headlamp UI

Replace the repoURL in all ArgoCD Application manifests with your fork:

  • argocd/apps/*.yaml

Then encrypt the vault:

cd ansible && ansible-vault encrypt inventory/group_vars/controlplane/vault.yml

Usage

make bootstrap   # one-time as root: create deploy user, enforce key-only SSH
make setup       # cluster + argocd + secrets (prompts for vault password)
make lint        # yamllint + ansible-lint
make scan        # secret detection (gitleaks) + IaC scanning (kubescape)

If your provider doesn't pre-install an SSH key for root, use --ask-pass for the initial bootstrap.

cd ansible && ansible-playbook playbooks/bootstrap.yml --ask-pass

Cluster Access

Copy the kubeconfig from the control plane:

scp ansible@<control-plane-ip>:/etc/rancher/k3s/k3s.yaml ~/.kube/config
# Then replace 127.0.0.1 with the control plane's public IP
  • ArgoCDadmin / password from: kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath='{.data.password}' | base64 -d
  • Grafanaadmin / password set via grafana_admin_password in vault
  • Headlamp — token from: kubectl create token headlamp --namespace headlamp

Note: These access methods (static credentials, kubeconfig with full cluster-admin, manually created tokens) are sufficient for initial setup and personal use — especially behind an IP allowlist. For long-term operation, consider centralized authentication (e.g. OIDC via an identity provider), RBAC-scoped kubeconfigs, and short-lived tokens to reduce the blast radius of leaked credentials.

About

A K8s homelab on public VPS. Built to learn from — or fork and make your own.

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks