Kubernetes Without the Headache: A Single-Node K3s Cluster on a Raspberry Pi

Cloud-native skills on twelve watts of power

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.

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.

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.

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:

sudo apt update && sudo apt full-upgrade -y

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:

sudo nano /boot/firmware/cmdline.txt

Add this to the end of that line (it must stay one line):

cgroup_memory=1 cgroup_enable=memory

On older images the file lives at /boot/cmdline.txt instead. Reboot to apply it:

sudo reboot

This is the part that feels like cheating. The official installer detects your architecture, downloads the binary, and sets up a systemd service:

curl -sfL https://get.k3s.io | sh -

Within a few seconds you have a running single-node cluster. Confirm the service is up and the node is ready:

sudo systemctl status k3s
sudo k3s kubectl get nodes

You should see your Pi listed with a status of Ready. To run kubectl without sudo, copy the generated config to your user account:

mkdir -p ~/.kube
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
sudo chown $USER ~/.kube/config

Now plain kubectl get nodes works.

Everything in Kubernetes is an object you can list, describe, and inspect. A handful of commands will carry you a long way:

kubectl get pods -A          # every pod in every namespace
kubectl get svc              # services in the default namespace
kubectl describe node        # detailed node status
kubectl logs <pod-name>      # a pod's logs

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.

Let us deploy a real web server and expose it. Create a file called nginx.yaml with a Deployment and a Service:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: nginx
          image: nginx:alpine
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 80

Apply it and watch the pods appear:

kubectl apply -f nginx.yaml
kubectl get pods -w

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.

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:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web
spec:
  rules:
    - host: web.pi.local
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 80

Apply it:

kubectl apply -f ingress.yaml

Point web.pi.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.

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:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: data
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

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.

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:

sudo cat /var/lib/rancher/k3s/server/node-token

On a second prepared Pi, run the agent installer pointed at the first node:

curl -sfL https://get.k3s.io | \
  K3S_URL=https://<server-ip>:6443 \
  K3S_TOKEN=<token> sh -

Back on the server, kubectl get nodes now lists both, and the scheduler spreads your pods across them. You have a multi-node cluster.

The cgroup edit above is the headline trap; skip it and K3s simply will not start, with cryptic errors about memory cgroups. Beyond that, 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.

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. Twelve watts, one memory card, and a genuine cloud-native playground. Break it, reflash it, and learn without fear.