Kubernetes Without the Headache: A Single-Node K3s Cluster on a Raspberry Pi
Cloud-native skills on twelve watts of power

Contents
The first Kubernetes cluster I ran at home cost me about forty pounds and drew less power than a night light. It was a single Raspberry Pi 4 with K3s on it, and within an evening it was serving a real web app through a real ingress controller — the same building blocks that front production clusters ten thousand times its size. That is the pitch in one sentence: you can learn the thing that runs the modern internet on hardware you would happily drop from a desk.
Kubernetes has a reputation for being magnificent and miserable in equal measure. It runs much of the modern internet, and it also reduces grown engineers to tears with its YAML, its jargon, and its sprawling list of moving parts. The good news is that you do not need a data centre, a cloud bill, or a team of platform engineers to learn it. You need a Raspberry Pi, a memory card, and an evening. K3s, a fully certified but dramatically slimmed-down Kubernetes distribution, will turn that little board into a real cluster you can poke at fearlessly. This guide takes you from a blank Pi to a running, internet-style deployment.
Why Learn Kubernetes on a Pi
There is a particular kind of learning that only happens on hardware you are not afraid to break. A Pi cluster is cheap, silent, and sips around twelve watts, so you can leave it running, wreck it, reflash it, and start again without a second thought. Unlike a cloud account, there is no meter ticking while you read the documentation.
Crucially, the skills transfer directly. The kubectl commands, the Deployment and Service objects, the ingress rules, and the troubleshooting habits you build on a Pi are exactly the ones you will use against a forty-node production cluster. Kubernetes does not care whether the node underneath it is an ARM board on your desk or a beefy server in a rack. The concepts are identical; only the scale differs.
K3s Versus Full Kubernetes
Upstream Kubernetes is a constellation of separate components: the API server, scheduler, controller manager, etcd for state, plus the kubelet and container runtime on every node. Standing it up by hand is a rite of passage precisely because it is fiddly.
K3s, built by the team at Rancher, packages all of that into a single binary under fifty megabytes. It swaps the heavyweight etcd for lightweight SQLite by default, bundles a container runtime, and ships with sensible extras already wired in: the Traefik ingress controller, a service load balancer, and a local-path storage provisioner. The result passes the same conformance tests as full Kubernetes, so it is not a toy or a fork; it is real Kubernetes with the awkward bits sanded off and a memory footprint that fits comfortably on a Pi.
Preparing the Pi
Start with a 64-bit operating system. Raspberry Pi OS Lite (64-bit) or Ubuntu Server work well; the Lite image keeps the desktop cruft off your cluster. Use the Raspberry Pi Imager to flash it, and while you are there, enable SSH and set a hostname so you can find the board on your network.
After first boot, SSH in and update the system:
| |
K3s needs the kernel to expose the memory cgroup, which Raspberry Pi OS does not enable by default. This is the single most common reason a Pi cluster fails to start, so deal with it now. Edit the boot command line file and append the cgroup parameters to the end of the existing single line:
| |
Add this to the end of that line (it must stay one line):
| |
On older images the file lives at /boot/cmdline.txt instead. Reboot to apply it:
| |
Installing K3s in One Line
This is the part that feels like cheating. The official installer detects your architecture, downloads the binary, and sets up a systemd service:
| |
Within a few seconds you have a running single-node cluster. Confirm the service is up and the node is ready:
| |
You should see your Pi listed with a status of Ready. To run kubectl without sudo, copy the generated config to your user account:
| |
Now plain kubectl get nodes works.
kubectl Basics
Everything in Kubernetes is an object you can list, describe, and inspect. A handful of commands will carry you a long way:
| |
get shows you what exists, describe explains why something is in the state it is, and logs tells you what an application is actually doing. When something breaks, those three are your first port of call.
Deploying a Sample App
Let us deploy a real web server and expose it. Create a file called nginx.yaml with a Deployment and a Service:
| |
Apply it and watch the pods appear:
| |
The Deployment asks for two replicas; Kubernetes schedules them, restarts them if they crash, and the Service gives them a single stable internal address. You have just declared the desired state of the world and let the cluster make it true.
Ingress with Bundled Traefik
A Service is reachable inside the cluster, but to hit it from a browser you want an ingress. K3s ships Traefik already running, so you only need an Ingress object. Add this to a file called ingress.yaml:
| |
Apply it:
| |
Point web.mylab.local at your Pi’s IP address (in your hosts file or local DNS) and the page loads through Traefik on port 80. That is the same routing pattern that fronts production clusters. On a single-node home setup the Pi’s own IP is where the ingress listens; on bigger bare-metal clusters you would hand that job to a load-balancer controller like MetalLB, which hands out real LoadBalancer IPs on your LAN the way a cloud provider would. You do not need it on one Pi, but it is the natural next piece the day you add a second.
Persistent Storage
Pods are ephemeral by design, so anything written inside one vanishes when it restarts. For data that must survive, K3s bundles the local-path provisioner. Request storage with a PersistentVolumeClaim and Kubernetes carves out a directory on the node automatically:
| |
Mount that claim into a pod and the data lives on the Pi’s disk independent of the pod’s lifecycle. For a single-node cluster this is perfect; for multi-node setups you would graduate to networked storage so a pod’s data follows it to whichever node it lands on.
Adding Worker Nodes Later
A single Pi is a complete cluster, but the joy of K3s is how trivially it grows. On your first Pi, fetch the cluster token:
| |
On a second prepared Pi, run the agent installer pointed at the first node:
| |
Back on the server, kubectl get nodes now lists both, and the scheduler spreads your pods across them. You have a multi-node cluster. There are a few wrinkles worth knowing before you go multi-node in earnest — how the token works, how the default CNI behaves across machines, and what suddenly breaks about local-path storage the moment a pod can land on a different node — and I walk through all of that in the companion piece on adding a second machine to your K3s cluster. For a first cluster, though, stay single-node; you will learn more by breaking one board than by debugging two.
Troubleshooting: What Actually Goes Wrong
The cgroup edit above is the headline trap; skip it and K3s simply will not start, with cryptic errors about memory cgroups. If sudo systemctl status k3s shows the service dead and journalctl -u k3s mentions failed to find memory cgroup or cgroup memory not enabled, that is your cmdline.txt edit missing or eaten. The classic mistake is putting the cgroup parameters on their own line — the file must stay one single line, space-separated, or the boot loader ignores everything after the break.
The second most common failure is silent and slow rather than loud. If pods take an age to start, kubectl feels sludgy, and the whole board seems to gasp under mild load, suspect the storage. A cheap microSD card will bottleneck the SQLite datastore K3s writes to constantly, and I have watched clusters that were fine for a day fall over once the card started wearing out. Check with:
| |
A pod stuck ContainerCreating for minutes is usually pulling a large image over a slow network, or hitting the ARM problem: if you see exec format error in kubectl describe pod, the image has no arm64 build and Docker handed you an amd64 binary the Pi cannot run. Pin a tag that publishes a multi-arch manifest, or pick a different image.
If Traefik never seems to serve your ingress, confirm it is actually running with kubectl get pods -n kube-system, then check that your hostname resolves to the Pi — a browser hitting web.mylab.local with no DNS entry fails long before it reaches the cluster, and the symptom looks identical to a broken ingress.
Gotchas Worth Knowing
Beyond the failures above, a handful of choices save future pain. Give the cluster a Pi with at least 2GB of RAM, ideally 4GB, since Kubernetes itself has overhead before your apps even arrive. Always use the 64-bit operating system, as some container images no longer publish 32-bit ARM builds. Run your workloads from a decent quality A2-rated memory card or, better still, boot from USB SSD, because cheap cards become a performance bottleneck and a reliability risk. And remember that ARM is not Intel: pull container images that publish arm64 variants, which most popular ones do these days.
Once the novelty of kubectl apply wears off, the honest reason home clusters wobble is rarely Kubernetes itself and usually the boring layer beneath — power, storage, and the odd wedged node at an inconvenient hour. If you want the grim comedy of how that plays out, and how to pre-empt it, I collected the war stories in why your Kubernetes cluster crashes at 2am.
Is It Worth It, and Who For
K3s turns Kubernetes from an intimidating data-centre beast into something you can install with one command and explore on a board that costs less than a nice dinner. You now have a real, conformant cluster, a deployed and exposed application, persistent storage, and a clear path to adding more nodes whenever curiosity strikes. The concepts you have practised here are the very same ones running behind the world’s largest services.
So is it worth an evening? If you want to learn Kubernetes — the objects, the failure modes, the muscle memory of debugging a wedged pod — a Pi and K3s is the best value on the table, bar none. If you want to run a handful of small self-hosted services with real reliability, honestly, plain Docker Compose on the same Pi is simpler and you should reach for Kubernetes only once you genuinely feel Compose’s limits: multi-node scheduling, self-healing, declarative rollouts. And if you are chasing production-grade high availability, one Pi is a single point of failure with a memory card for a heart — treat this as a laboratory, not a load-bearing wall.
For the person who wants cloud-native skills without a cloud bill, though, it is close to ideal. Twelve watts, one memory card, and a genuine cloud-native playground. Break it, reflash it, and learn without fear.




