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

UseConfigurationMassiveGRID VPS
Learning, single node1 vCPU / 2 GB / 32 GB$4.79/mo
Small workloads, single node2 vCPU / 4 GB / 64 GB$9.58/mo
Server plus two agents3 × 2 vCPU / 4 GB / 64 GB$28.74/mo
HA embedded etcd, three servers3 × 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:

PortPurpose
6443/tcpKubernetes API. Agents and kubectl need it
8472/udpFlannel VXLAN, between nodes only
10250/tcpKubelet metrics, between nodes only
2379–2380/tcpEmbedded 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.

BundledKeep it whenReplace it when
Traefik ingressYou want ingress working immediatelyYou standardised on ingress-nginx, or need features Traefik lacks
ServiceLB (Klipper)Single node, or you want LoadBalancer services to just workMulti-node on a real network, where MetalLB is a better fit
local-path storageData can live on one node's diskPods must reschedule and keep their data
SQLite datastoreOne server node, everYou 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

SymptomCause
Agent will not joinPort 6443 closed, or the token was copied with a trailing newline
Pods stuck in ContainerCreatingCNI traffic blocked. Check 8472/udp between nodes
Volume empty after a pod moveslocal-path provisioner. Data was on the previous node
Certificate error from a remote kubectlHostname missing from the certificate. Reinstall with --tls-san
LoadBalancer service pendingServiceLB disabled with no replacement, or no free host port
Node NotReady under loadMemory 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.

Further Reading