5 min read

๐Ÿ“ My Raspberry Pi Kubernetes Homelab - A Real Production-Like Setup with k3s

Rasberry Pi
Photo by Satyajit Das

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 NotReady node 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 portfolio OutOfSync 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.