Pi Hard: Raspberry-Flavored Kubernetes
Intro
Kubernetes is often associated with large-scale deployments, enterprise applications, and complex cloud infrastructures. But what happens when you bring it home—literally? That’s exactly what we set out to explore by building a Kubernetes cluster out of a few Raspberry Pi 5s.
Our motivation? Learning Kubernetes, hosting lambdasandlapdogs.com, and, most importantly, having fun experimenting with tech. Is it the most practical setup? Probably not. But if it runs reliably, doesn’t generate too much heat or noise, and teaches us something along the way, we’ll call it a win.
Here’s how we did it, what powers the cluster, and why it’s more about the journey than the destination.
The Hardware
At the heart of our cluster are three Raspberry Pi 5s, each fitted with Waveshare PoE+ M.2 HATs and 256 GB NVMe SSDs for storage. This configuration makes the cluster efficient, cable-light, and easy to manage, as all power is delivered through a PoE-capable unmanaged switch.
We named the nodes as follows:
- rpi-k8s-master: The control plane node
- rpi-k8s-node1: Worker node 1
- rpi-k8s-node2: Worker node 2
Building the cluster involved some hands-on assembly: attaching the HATs, securing the SSDs, and connecting the Pi’s to the switch. The result? A compact, quiet, and surprisingly capable Kubernetes cluster.

The Kubernetes Config
Rather than deploying full Kubernetes, we opted for k3s—a lightweight Kubernetes distribution designed for resource-constrained environments. Its simplicity and efficiency made it a perfect fit for Raspberry Pi 5s, and it allowed us to focus on learning the core concepts of Kubernetes without unnecessary overhead.
Building and Pushing the Docker Image
We used Docker Buildx to build the blog image for the ARM64 architecture and pushed it to a private GitHub Container Registry (GHCR):
docker buildx build --platform linux/arm64 -t ghcr.io/{username}/{repo}:latest --push .
Retrieving Our Image
Once the image was built and pushed, we pulled it onto the cluster using the following command:
sudo ctr image pull ghcr.io/{username}/{repo}:latest
Deployment
The deployment.yaml file defines the application pods, replicas, and the container image. Here’s a sanitized example:
apiVersion: apps/v1
kind: Deployment
metadata:
name: flask-blog-deployment
spec:
replicas: 2
selector:
matchLabels:
app: flask-blog
template:
metadata:
labels:
app: flask-blog
spec:
containers:
- name: blog
image: ghcr.io/<username>/<repo>:latest
ports:
- containerPort: 5000
Service
The service.yaml file exposes the application to the cluster, allowing internal traffic to reach the appropriate pods. The service is named flask-blog:
apiVersion: v1
kind: Service
metadata:
name: flask-blog
spec:
selector:
app: flask-blog
ports:
- protocol: TCP
port: 80
targetPort: 5000
type: ClusterIP
Ingress
Traefik handles ingress routing, mapping external traffic to the appropriate service. Here’s an example from ingress.yaml:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: flask-blog-ingress
spec:
rules:
- host: blog.lambdasandlapdogs.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: flask-blog
port:
number: 80
Lights, Camera, Action
Quick aside: No one wants to rebuild an image manually every time main is updated, so we went ahead and setup a GitHub Action to handle this bit of business for us:
name: Build and Push to GHCR
on:
push:
branches:
- main
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
# Step 1: Check out the repository
- name: Checkout code
uses: actions/checkout@v3
# Step 2: Set up QEMU for cross-platform builds
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
# Step 3: Set up Docker Buildx
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
# Step 4: Log in to GitHub Container Registry
- name: Log in to GHCR
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Step 5: Build and push the Docker image
- name: Build and Push Docker image
run: docker buildx build --platform linux/arm64 -t ghcr.io/{repo}:latest --push .
Tunneling with Cloudflare
To expose the cluster to the internet, we use a Cloudflare tunnel. This lightweight, secure solution handles external traffic routing and provides additional benefits like:
- TLS Termination: Ensuring all traffic is encrypted without needing a separate certificate management process.
- DDoS Protection: Adding a layer of security to mitigate malicious traffic.
- Ease of Use: Simplifying external access to the cluster without opening router ports.
Here’s the flow:
1. External traffic (WAN) reaches the Cloudflare tunnel.
2. The tunnel routes traffic to Traefik.
3. Traefik directs traffic to the appropriate k3s service.
We initially created the tunnel locally following this guide. Once the tunnel was running, we transitioned to managing it through the Cloudflare dashboard for easier maintenance.
Here’s an example of creating and running a tunnel:
cloudflared tunnel create my-tunnel
cloudflared tunnel route dns my-tunnel blog.lambdasandlapdogs.com
cloudflared tunnel run my-tunnel
To ensure traffic reaches the tunnel, we added a DNS record in Cloudflare that points blog.lambdasandlapdogs.com to the tunnel. This setup ensures secure, seamless routing of external requests to the cluster.
Current Status and Future Plans
The cluster is live and hosting lambdasandlapdogs.com. However, it’s still in an evaluation phase as we monitor heat, noise, and overall reliability.
If all goes well, we plan to:
1. Transition the blog fully from PythonAnywhere to the cluster.
2. Open-source the blog code and clean up the Dockerfile for public use.
3. Continue experimenting with Kubernetes and share our findings.

Rescources and Recs
For readers interested in building a similar setup or learning Kubernetes, here are some recommended resources:
- k3s Documentation
- Kubernetes Official Documentation
- Cloudflare Tunnel Docs
Wrapping Up
Building a Kubernetes cluster at home might not be crazy practical, but it’s incredibly rewarding. From learning Kubernetes to running a live blog on a set of Raspberry Pis, this project blended technical complexity with genuine fun.
Whether you’re exploring Kubernetes for the first time or looking for a hands-on project, a home cluster is an exciting way to dive in. Feel free to reach out with questions or just to share your own cluster-based projects!