k3s puts a certified Kubernetes distribution in a single 70 MB binary, and installs in one command. The command also makes four decisions for you: which ingress controller, which load balancer, which storage provisioner and which datastore. This guide covers the install, the defaults worth overriding before you have workloads depending on them, and the point at which a single server node stops being enough.
The install is one command and takes under a minute. What that command quietly decides for you is the interesting part: k3s ships with an ingress controller, a load-balancer implementation, a storage provisioner and a database, and each of those defaults is right for some deployments and wrong for others.
MassiveGRID Ubuntu VPS: Ubuntu 24.04 LTS · Proxmox HA cluster with automatic failover · Ceph 3x replicated NVMe storage · independent CPU, RAM and storage scaling · 85+ metros across 30+ countries, auto-provisioned in New York, London, Frankfurt and Singapore
Cloud VPS — from $1.99/mo
Managed Kubernetes — from $0.03474/h, about $25.37/mo · Dedicated VPS for guaranteed cores
What k3s Removes
k3s is a certified Kubernetes distribution in a single binary of about 70 MB. It reaches that size by replacing rather than omitting: etcd becomes SQLite for single-server installs, the cloud controller and legacy in-tree providers are stripped out, and the control plane components run in one process instead of several.
The API is standard Kubernetes. Manifests, Helm charts and kubectl all behave normally, which is the point. What differs is the operational surface: fewer moving parts, one systemd unit, and a control plane that runs comfortably in half a gigabyte.
Sizing
| Use | Configuration | MassiveGRID VPS |
|---|---|---|
| Learning, single node | 1 vCPU / 2 GB / 32 GB | $4.79/mo |
| Small workloads, single node | 2 vCPU / 4 GB / 64 GB | $9.58/mo |
| Server plus two agents | 3 × 2 vCPU / 4 GB / 64 GB | $28.74/mo |
| HA embedded etcd, three servers | 3 × 4 vCPU / 8 GB / 64 GB | $55.56/mo |
The published minimum is 512 MB for a server and 256 MB for an agent. Those numbers describe k3s itself and leave nothing for your workloads, so treat 2 GB as the realistic floor and 4 GB as comfortable.
Preparing the Host
Two things before installing. Disable swap, because the kubelet expects it off and the alternative is a flag you then carry forever:
swapoff -a
sed -i '/ swap / s/^/#/' /etc/fstab
Then open the ports the cluster needs, and only those. On a single node with no agents you need nothing but SSH and whatever your applications serve:
| Port | Purpose |
|---|---|
| 6443/tcp | Kubernetes API. Agents and kubectl need it |
| 8472/udp | Flannel VXLAN, between nodes only |
| 10250/tcp | Kubelet metrics, between nodes only |
| 2379–2380/tcp | Embedded etcd, only in HA mode |
Restrict the inter-node ports to the other nodes' addresses rather than opening them broadly. An exposed kubelet port is a route to running containers on your cluster.
Installing
curl -sfL https://get.k3s.io | sh -
systemctl status k3s
k3s kubectl get nodes
If piping a remote script to a shell is not acceptable in your environment, download it, read it, then run it. The script adds an apt or yum repository, installs the binary, writes a systemd unit and starts it.
The kubeconfig lands at /etc/rancher/k3s/k3s.yaml, readable only by root. To use a normal kubectl as your own user, copy it and rewrite the server address:
mkdir -p ~/.kube
cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
chown "$USER:$USER" ~/.kube/config
chmod 600 ~/.kube/config
sed -i "s/127.0.0.1/$(curl -s ifconfig.me)/" ~/.kube/config
That last line matters when you administer the cluster from your laptop. The bundled config points at localhost, which works on the node and nowhere else.
The Defaults Worth Changing
k3s bundles four components most guides never mention. Each is genuinely useful and each is worth a deliberate decision.
| Bundled | Keep it when | Replace it when |
|---|---|---|
| Traefik ingress | You want ingress working immediately | You standardised on ingress-nginx, or need features Traefik lacks |
| ServiceLB (Klipper) | Single node, or you want LoadBalancer services to just work | Multi-node on a real network, where MetalLB is a better fit |
| local-path storage | Data can live on one node's disk | Pods must reschedule and keep their data |
| SQLite datastore | One server node, ever | You want a highly available control plane |
Disable what you do not want at install time, because changing it afterwards means uninstalling components the cluster is already using:
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="\
--disable traefik \
--disable servicelb \
--write-kubeconfig-mode 640 \
--tls-san k8s.example.com" sh -
--tls-san adds a hostname to the API server certificate. Omit it and connecting to the API by DNS name later produces a certificate error, and fixing that means regenerating certificates.
The local-path provisioner is the default that surprises people most. It writes to /var/lib/rancher/k3s/storage on whichever node the pod landed on, so a rescheduled pod finds an empty volume. That is fine for a cache and wrong for a database.
Adding Agent Nodes
Take the token from the server:
cat /var/lib/rancher/k3s/server/node-token
Then on each agent:
curl -sfL https://get.k3s.io | \
K3S_URL=https://10.0.0.1:6443 \
K3S_TOKEN=the-token-you-just-copied sh -
Use private addresses for inter-node traffic rather than public ones. Cluster traffic on a public interface is both slower and exposed, and k3s will happily use whatever you give it.
Making the Control Plane Highly Available
SQLite means one server node. Lose it and the cluster's workloads keep running, but nothing can be scheduled, changed or recovered until it returns. For a control plane that survives a node failure, use embedded etcd, which needs an odd number of servers, three at minimum.
Initialise the first:
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server \
--cluster-init \
--tls-san k8s.example.com" sh -
Then join the second and third as servers rather than agents:
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server \
--server https://10.0.0.1:6443 \
--tls-san k8s.example.com" \
K3S_TOKEN=the-token sh -
Two properties of etcd quorum to internalise before relying on this. Three nodes tolerate one failure; a fourth adds none, because four requires three for a majority just as three does. And losing two of three leaves the cluster without quorum, at which point the API stops accepting writes to protect itself. Our guide to building a three-node HA Kubernetes cluster covers the arithmetic and the API endpoint design in full.
Verifying the Cluster
kubectl get nodes -o wide
kubectl get pods -A
kubectl create deployment web --image=nginx --replicas=3
kubectl expose deployment web --port=80 --type=LoadBalancer
kubectl get svc web
Two checks are worth doing beyond seeing pods run. Confirm every node reports Ready rather than assuming, because a node that joined but cannot reach the API sits in NotReady and quietly takes no work. And delete a pod to confirm it is recreated, which proves the control plane is actually reconciling rather than merely running.
Common Failures
| Symptom | Cause |
|---|---|
| Agent will not join | Port 6443 closed, or the token was copied with a trailing newline |
Pods stuck in ContainerCreating | CNI traffic blocked. Check 8472/udp between nodes |
| Volume empty after a pod moves | local-path provisioner. Data was on the previous node |
| Certificate error from a remote kubectl | Hostname missing from the certificate. Reinstall with --tls-san |
| LoadBalancer service pending | ServiceLB disabled with no replacement, or no free host port |
Node NotReady under load | Memory pressure. The kubelet evicts before the kernel does |
To start again, k3s uninstalls cleanly, which is one of its better properties:
/usr/local/bin/k3s-uninstall.sh # server
/usr/local/bin/k3s-agent-uninstall.sh # agent
When to Use Managed Kubernetes Instead
k3s is the right answer when you want to understand the cluster, when the cluster is small, or when you need Kubernetes on hardware you already have. It is a poor answer when nobody on the team wants to own etcd backups, certificate rotation and version upgrades.
Those three tasks are the real cost of self-managed Kubernetes, and they arrive on a schedule rather than when convenient. Certificate expiry in particular breaks clusters that have been running untouched for a year.
MassiveGRID managed Kubernetes starts at $0.03474 per hour, roughly $25.37 a month, and bills on the RAM and CPU your pods actually consume rather than the size of the servers underneath, measured in cloudlets of 128 MiB and 400 MHz. Clusters come with an HA control plane, redundant API servers and etcd, Traefik or nginx ingress with TLS termination, horizontal and vertical pod autoscaling, and dynamic persistent volume provisioning already configured.
Either way the platform underneath is the same: Proxmox high-availability clustering with automatic failover, Ceph storage replicating every block three times across independent NVMe drives, and placement in any of 85+ metros across 30+ countries. Deploy a Cloud VPS to run k3s yourself, or compare requirements in our sizing guide.