Installing K3s on a Raspberry Pi Cluster
Step by step: prepare ARM nodes, install the K3s server, join agents with the node token, and verify a working multi-node Kubernetes cluster on Raspberry Pi hardware.
Published on • November 14, 2026
AI Assistant

Three Raspberry Pis, a network switch, and one curl command will get you a working multi-node Kubernetes cluster in about ten minutes. This is the standard first real project for K3s, and it’s a good way to learn cluster mechanics on hardware you can hold in one hand.
Prerequisites
Before touching a Pi, settle these:
- 64-bit OS on every node. Use Raspberry Pi OS 64-bit (or Ubuntu 24.04 for ARM64). K3s supports arm64 and armv7, but mixing 32-bit and 64-bit nodes in one cluster invites subtle image-pull failures — keep them identical. Check with
uname -m(you wantaarch64). - Same L2 network. All nodes on one LAN/VLAN, no client isolation on the Wi-Fi. Wired Ethernet is strongly preferred; Wi-Fi drops cause node-not-ready flapping that looks exactly like a Kubernetes bug.
- Swap off. Kubelet expects no swap. Raspberry Pi OS images may enable
dphys-swapfile; disable it, or use a cloud image that doesn’t enable swap. - Cloud-init disabled or consistent. If you clone SD card images, cloud-init will regenerate SSH host keys and — worse — can collide on identity. Either disable it or
cloud-init cleaneach clone. - A fixed IP (or DHCP reservation) for the server node. Agents join by server address; a moving DHCP lease breaks registration later.
- SSH keys set up so you can script the join.
Say the server is 192.168.1.10 and the two agents are .11 and .12.
Install the server
On the first Pi:
curl -sfL https://get.k3s.io | sh -
That’s the entire server install. The install script drops a systemd unit, starts k3s server, and writes the kubeconfig to /etc/rancher/k3s/k3s.yaml. Verify:
sudo k3s kubectl get nodes
You should see one node in Ready. Give it a moment — CoreDNS, Traefik, and metrics-server deploy themselves on first start, and on a Pi 3 that first boot can take a minute or two.
Two files matter from here on:
# the credential agents use to join
sudo cat /var/lib/rancher/k3s/server/node-token
# the API endpoint agents connect to
# https://192.168.1.10:6443
Treat that token like a password. Anyone with it can join a machine to your cluster.
Join the agents
On each agent Pi:
curl -sfL https://get.k3s.io | K3S_URL=https://192.168.1.10:6443 K3S_TOKEN=<node-token> sh -
This installs the binary and starts k3s agent rather than k3s server — no control plane, no datastore, just kubelet + containerd + CNI registering upstream. The equivalent manual invocation, if you’d rather run it yourself, is:
k3s agent --server https://192.168.1.10:6443 --token <node-token>
Back on the server:
sudo k3s kubectl get nodes
Three nodes, all Ready. If an agent never appears, check journalctl -u k3s-agent -f on it — the usual culprits are a wrong token, the server’s 6443 port blocked, or a duplicate hostname.
kubectl on your laptop
Copy the kubeconfig off the server and rewrite the server address so your laptop can reach it (the file says 127.0.0.1 by default):
scp pi@192.168.1.10:/etc/rancher/k3s/k3s.yaml ~/.kube/pi-cluster.yaml
sed -i 's/127.0.0.1/192.168.1.10/' ~/.kube/pi-cluster.yaml
kubectl --kubeconfig ~/.kube/pi-cluster.yaml get nodes
(On macOS use sed -i ''.) The cert in that file is valid for the hostname k3s, so you’ll want to switch to a proper server: URL and regenerate, or just live with kubectl --kubeconfig for a lab.
Label your nodes
Labels turn three identical Pis into a topology you can schedule against:
kubectl label node pi-2 node-role.kubernetes.io/worker-
kubectl label node pi-1 disk=ssd
kubectl label node pi-2 disk=sd
kubectl get nodes -L disk
Then a DaemonSet or a pod with nodeSelector: {disk: ssd} lands where you intended.
Deploy a test workload
Prove scheduling and networking end to end:
kubectl create deployment web --image=nginx --replicas=3
kubectl expose deployment web --port=80 --type=NodePort
kubectl get pods -o wide
-o wide should show pods spread across all three nodes. Hit any Pi’s IP on the printed NodePort and you get nginx. That confirms the CNI is routing pod traffic between physical boxes, not just on one host.
Upgrading later
Because everything ships in one binary, upgrades are uniform across the cluster. Update the install script’s channel (or pin a version), then upgrade server first, agents after:
curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION=v1.xx.x sh -
Do the server, confirm kubectl get nodes shows it back Ready with pods rescheduled, then roll the agents one at a time. Agents draining mid-upgrade only affect that node’s pods — the others keep serving, and the client-side load balancer in each agent rides through a server restart. The failure mode to avoid is upgrading all four boxes simultaneously: that’s not an outage waiting to happen, it’s an outage.
Pi-specific gotchas
- SD card wear. Kubernetes writes constantly: container logs, kubelet state, etcd/SQLite, image layers. A cheap SD card will die. Use
log rotatelimits, point containerd’s data dir at a USB/NVMe mount, or better — boot from NVMe on a Pi 5/CM4 with an SSD. This is the single biggest reliability difference between a toy cluster and one that runs for a year. - Power. Undervoltage throttling shows up as random node restarts and mysterious CPU throttling under load. Use the official PSU, and powered hubs for anything USB-attached.
dmesg | grep -i voltagetells you if you’ve been throttling. - Memory and cgroups. A Pi with 1–2 GB RAM is fine for agents, marginal for a server. Watch
kubectl top nodes. Reserve memory for the OS, and make sure cgroup v1 vs v2 matches what your OS image ships — K3s handles both, but hand-rolled images sometimes don’t. - Thermals. A passively cooled Pi under sustained image pulls will throttle. A $5 heatsink isn’t optional.
- Clock drift. Certs and token validation get weird when a node’s clock is minutes off. Enable NTP (
systemd-timesyncdis usually enough).