๐ My Raspberry Pi Kubernetes Homelab - A Real Production-Like Setup with k3s
Everyone starts with a tutorial cluster.
I wanted something I'd actually learn from โ a setup that mirrors what I work with at my day job. Real nodes. Real networking. Real workloads. Real failures at 11pm.
This is not a "hello world on a Pi" post. This is the story of the homelab I've been running for over 7 months โ what I built, how it's structured, and why I did it this way.
๐ค Why Build a Homelab at All?
When you work on Kubernetes at scale professionally, the worst thing you can do is only touch it at work.
At work, there are guardrails. There's a team. There are runbooks. You don't get to break things freely and learn from the wreckage.
A homelab gives you:
- โ Freedom to break things without consequences
- โ Real hardware behaviour you'll never see in a managed cloud cluster
- โ Always-on environment to run actual workloads
- โ A place to test patterns before bringing them to production
- โ Zero cloud bill
๐ก The best part? Every mistake I make here makes me a better engineer at work. Debugging a
NotReadynode on physical hardware at midnight teaches you more than any certification.
๐ฅ๏ธ The Hardware โ What I'm Actually Running
My cluster has 5 nodes across two types of hardware:
Raspberry Pi Nodes
| Node | Role | OS | Uptime |
|---|---|---|---|
zeta-core |
control-plane + master | Ubuntu 24.04 LTS (arm64) | 231 days |
zeta-lite |
worker | Debian 12 Bookworm (arm64) | 230 days |
zeta-macro |
worker | Debian 12 Bookworm (arm64) | 222 days |
dietpi |
worker | Debian 13 Trixie (arm64) | 81 days |
Intel NUC Node
| Node | Role | OS | Notes |
|---|---|---|---|
shark-server |
control-plane + etcd + worker | Ubuntu 24.04 LTS (x86_64) | Added recently as second control plane |
๐ต Mixed architecture cluster โ ARM64 Raspberry Pis and x86_64 NUC running together in the same k3s cluster. k3s handles this seamlessly.
The NUC joined 16 days ago as a second control plane, giving me proper etcd HA for the first time. Before that, zeta-core was doing everything alone.
โ๏ธ The Software Stack
Here's what's running across the cluster right now:
Core Infrastructure
| Tool | Purpose | Namespace |
|---|---|---|
| k3s v1.35 | Kubernetes distribution | โ |
| Traefik | Ingress controller (bundled with k3s) | kube-system |
| MetalLB | Bare-metal load balancer | metallb-system |
| cert-manager | Automatic TLS via Let's Encrypt | cert-manager |
| local-path-provisioner | Dynamic PVC provisioning | kube-system |
GitOps & Deployments
| Tool | Purpose | Namespace |
|---|---|---|
| ArgoCD | GitOps โ everything deploys from Git | argocd |
Observability
| Tool | Purpose | Namespace |
|---|---|---|
| Prometheus | Metrics collection | monitoring |
| Grafana | Dashboards & visualisation | monitoring |
| kube-state-metrics | Kubernetes object metrics | monitoring |
| node-exporter | Node-level metrics (all 5 nodes) | monitoring |
| Alertmanager | Alerting | monitoring |
Workloads
| App | URL | Namespace |
|---|---|---|
| Ghost CMS | blog.rootbysatya.in | ghost |
| Portfolio site | rootbysatya.in | portfolio |
| ArgoCD UI | argocd.rootbysatya.in | argocd |
| Grafana | grafana.rootbysatya.in | monitoring |
๐ How Networking Works
This is the part most homelab guides skip. Let me show you exactly how a request reaches my blog.
Internet โ Router โ MetalLB (192.168.50.240) โ Traefik โ Ghost Pod
MetalLB assigns a real LAN IP (192.168.50.240) to the Traefik LoadBalancer service. This is what makes bare-metal load balancing work โ without MetalLB, LoadBalancer type services stay stuck in <pending> forever on a non-cloud cluster.
Traefik receives all traffic on that IP and routes it based on hostname:
blog.rootbysatya.in โ ghost namespace โ ghost pod
rootbysatya.in โ portfolio namespace โ frontend pod
argocd.rootbysatya.in โ argocd namespace โ argocd-server pod
grafana.rootbysatya.in โ monitoring namespace โ grafana pod
cert-manager handles TLS automatically. Every ingress gets a real Let's Encrypt certificate โ no manual renewal, no self-signed certs, no warnings in the browser.
๐ How k3s is Installed
Single command on the master node:
curl -sfL https://get.k3s.io | sh -
But before that, on every Raspberry Pi node, cgroups must be enabled:
# Edit boot config
sudo nano /boot/firmware/cmdline.txt
# Append to the END of the existing single line (don't add a newline):
# cgroup_memory=1 cgroup_enable=memory
sudo reboot
โ ๏ธ Skip this and the kubelet silently fails. This is the #1 thing that trips people up on Pi.
Joining worker nodes to the cluster:
# On master โ get the node token
sudo cat /var/lib/rancher/k3s/server/node-token
# On each worker node
curl -sfL https://get.k3s.io | \
K3S_URL=https://MASTER_IP:6443 \
K3S_TOKEN=YOUR_TOKEN \
sh -
For the NUC as second control plane (etcd HA):
curl -sfL https://get.k3s.io | \
K3S_URL=https://MASTER_IP:6443 \
K3S_TOKEN=YOUR_TOKEN \
INSTALL_K3S_EXEC="server" \
sh -
๐ Current Resource Usage
Here's the live resource consumption across my 5 nodes:
NAME CPU(cores) CPU(%) MEMORY MEMORY(%)
dietpi 150m 3% 444Mi 46%
shark-server 403m 20% 1797Mi 46%
zeta-core 436m 10% 4154Mi 52%
zeta-lite 107m 2% 511Mi 53%
zeta-macro 156m 3% 343Mi 37%
zeta-core carries the most load โ it runs ArgoCD, Prometheus, Grafana, MetalLB controller, and metrics-server. It's the heart of the cluster.
shark-server (NUC) is at 20% CPU โ that's cert-manager, Ghost, and being a second control plane simultaneously.
๐ GitOps โ Everything Deploys from Git
The most important decision I made: nothing is deployed manually.
Every workload โ Ghost, the portfolio site, monitoring stack โ is managed by ArgoCD and lives in a Git repository. A git push is the only way anything changes in the cluster.
Git repo (github.com/Satya-jit/Personal-folio)
โ
ArgoCD watches for changes
โ
Syncs to cluster automatically
โ
Workload updated
ArgoCD status right now:
NAME SYNC STATUS HEALTH STATUS
ghost Synced Healthy โ
portfolio OutOfSync Healthy โ ๏ธ
portfolio is OutOfSync โ means there's a drift between what's in Git and what's running. That's next on my fix list.
๐พ Storage
I use k3s's built-in local-path-provisioner for persistent storage. It dynamically provisions hostPath volumes on whichever node schedules the pod.
Current PVCs:
ghost/ghost-content โ 10Gi (Ghost CMS content)
๐ Important: local-path storage is node-local. If the pod moves to a different node, it loses its data. For a homelab this is fine. For anything stateful in production, look at Longhorn โ also from Rancher, installs on k3s with Helm, and gives you replicated storage across nodes.
๐ง Lessons from 7+ Months of Running This
Use static IPs everywhere. DHCP leases expire. A Pi reboots, gets a new IP, kubeconfig breaks, nothing works. Reserve every node's MAC address in your router.
Mixed OS is fine, mixed architecture needs care. ARM64 and x86_64 in the same cluster works great with k3s. But your container images must support both architectures โ always check for multi-arch support before deploying.
etcd on SD card is risky. etcd is extremely write-heavy. Pi SD cards wear out fast under this load. I run my master node (zeta-core) from a USB SSD. The workers on SD cards are fine since they don't run etcd.
ArgoCD OutOfSync is not always urgent. It means drift exists. Understand why before blindly syncing โ sometimes it's an intentional manual change you haven't committed to Git yet.
Traefik + MetalLB is a powerful combo. Once configured, every new ingress you create automatically gets routing + TLS. Adding a new service to the cluster is a 10-line YAML file.
๐ญ What's Next for This Cluster
- ๐ง Fix the
portfolioOutOfSync in ArgoCD - ๐ฆ Add Longhorn for replicated storage across nodes
- ๐ Implement proper secret management (Sealed Secrets or External Secrets)
- ๐ Set up proper Alertmanager rules and notification channels
- ๐งช Add a staging namespace for testing before deploying to production workloads
๐ฌ Final Thought
This homelab runs my blog, my portfolio, my monitoring, and my GitOps pipeline โ all from a handful of Raspberry Pis and one NUC, sitting on my desk.
It's not a toy. It's a real system I depend on and actively maintain.
And every hour I spend on it makes me sharper at my actual job.
That's the point of a homelab.
Want to build something similar? Drop your questions in the comments.
โ Satya
๐จโ๐ป Platform Engineer | Kubernetes | GitOps | Homelab
๐ Next post: Setting up ArgoCD for GitOps โ how I deploy everything on this cluster with a single
git push.
Follow RootBySatya to get notified.
Member discussion