Beyond the laptop: k3s, MicroK8s, k0s, Talos
A cluster on your own server or VPS: what to install, which ports to open, and why kubeadm is worth touching only to understand the control plane.
Why
A cluster in containers on your laptop is great for quick checks. It won't survive you closing the lid, won't hand a service to a coworker, and won't show you how real nodes behave on a real network. The moment you need an always-on cluster with an external address, the conversation moves to servers: a home machine, an old mini PC, or a VPS for a few dollars.
Below are six options, from the simplest to the most instructive. You don't need to install any of them on your laptop for this lesson. In the tasks you'll touch what you can honestly check locally: the shape of the control plane that kubeadm assembles on a server, and k3s's built-in load balancer.
Concepts
A Kubernetes distribution is a ready-made build of cluster components with an installer. k3s, MicroK8s and k0s pack the control plane into one process or one binary. kubeadm doesn't pack anything: it lays out the components as separate Pods, and you can see them.
A static Pod is a Pod that kubelet runs on its own from a YAML file in a
directory on the node, without the API server involved. This is how
kubeadm runs apiserver, controller-manager, scheduler and etcd. In
kubectl get pods they show up as mirror Pods, and you can't delete them
through kubectl: kubelet brings them right back up as long as the file
sits in the directory.
| Option | Runs on | Second node | What's already inside |
|---|---|---|---|
| k3s | Linux only | K3S_URL and K3S_TOKEN | Traefik, ServiceLB, local-path |
| MicroK8s | Linux directly, macOS and Windows through a VM | microk8s add-node, HA with three nodes | add-ons with one command |
| k0s | Linux only | k0sctl over SSH | StorageClass installed separately |
| Talos | its own OS, no SSH or shell | --controlplanes, --workers | managed only through talosctl |
| vcluster | on top of an existing cluster | nodes come from the host | its own control plane in a namespace |
| kubeadm | Linux only | kubeadm join | CNI and StorageClass installed separately |
k3s on your own server or VPS
Version v1.36.4+k3s1 from 2026-08-27, the stable channel points to the
same one. Works only on Linux: x86_64, armhf, arm64.
Requirements are modest: the server needs 2 CPU and 2 GB RAM, an agent is fine with 1 CPU and 512 MB. Ports: TCP 6443 for the API server, UDP 8472 for flannel VXLAN between nodes, TCP 10250 for kubelet. All nodes need different hostnames, otherwise nodes overwrite each other.
curl -sfL -o install-k3s.sh https://get.k3s.io
less install-k3s.sh
INSTALL_K3S_VERSION=v1.36.4+k3s1 sh install-k3s.sh
cat /var/lib/rancher/k3s/server/node-token
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
Without INSTALL_K3S_VERSION the script installs whatever the stable
channel currently points to. On a server that needs to behave the same
way six months from now, it's better to pin the version.
The second node joins the first using the API server's address and the
token from the file above. Download the install-k3s.sh script onto it
the same way you did for the first node:
K3S_URL=https://server:6443 K3S_TOKEN=$TOKEN sh install-k3s.sh
Everything is removed with a single script: /usr/local/bin/k3s-uninstall.sh.
What k3s brings with it
ServiceLB (also known as klipper-lb) handles Services of type
LoadBalancer. For every such Service, k3s creates a DaemonSet in
namespace kube-system, with Pods named svclb-<Service name>-…. These
Pods claim a hostPort equal to the Service's port, and land only on nodes
where that port is free. Hence the typical picture: a second
LoadBalancer on the same port never gets a Pod, it sits in Pending, and
the Service's EXTERNAL-IP column stays <pending>.
ServiceLB is disabled with the flag --disable=servicelb. This is a
required step if you're installing MetalLB.
Traefik v3 is rolled out automatically and takes ports 80 and 443. Its
chart lives in
/var/lib/rancher/k3s/server/manifests/traefik.yaml, but you can't edit
that file directly: settings are set through the HelmChartConfig
resource. Traefik is fully removed with --disable=traefik. Gateway API
is enabled with the value providers.kubernetesGateway.enabled: true,
Gateway API v1.4 is supported.
The StorageClass is called local-path and is marked default, data lands
in /var/lib/rancher/k3s/storage.
There's a non-obvious gotcha tied to this StorageClass. Manifests from
the server/manifests directory are reapplied on every k3s start. So
local-path reinstates its own default status even if you made another
class default. You install Longhorn, make it default, restart the
server, and you're back to two candidates, or back to local-path as
default.
A few other odds and ends: you'll need to configure firewalld and ufw for
the ports above. On a Raspberry Pi you need to add cgroup_memory=1 cgroup_enable=memory to cmdline. On RHEL 10 you need
dnf install -y kernel-modules-extra.
MicroK8s
A distribution from Canonical, installed via snap. The current track is 1.36, which carries v1.36.2. The project is active.
The first snag you hit is in the docs: the getting-started page is behind
and shows --channel=1.35. You should specify the channel explicitly.
sudo snap install microk8s --classic --channel=1.36
sudo usermod -a -G microk8s $USER
mkdir -p ~/.kube && chmod 0700 ~/.kube
su - $USER
microk8s status --wait-ready
microk8s kubectl get nodes
Lifecycle management: microk8s stop, microk8s start, microk8s reset.
Linux installs directly, while Windows and macOS only through a virtual machine (Multipass or WSL2). Works on Raspberry Pi.
MicroK8s's strength is its add-ons, enabled with a single command:
microk8s enable dns hostpath-storage ingress metrics-server registry
A built-in image registry comes up on localhost:32000. An extra set of
add-ons is enabled through microk8s enable community.
In 1.36 the dashboard, minio and kwasm add-ons lost support.
Kubernetes Dashboard is gone from MicroK8s, and practically every old
tutorial that started with microk8s enable dashboard breaks right there.
Multi-node is set up simply: microk8s add-node on the first node prints
a ready-made join command for the second. With three or more nodes, HA
turns on by itself, no separate setup needed. The nodes here are real
machines or VMs, not containers.
Resource-wise, the minimum is 540 MB RAM; 4 GB RAM and 20 GB of disk
are recommended. The StorageClass comes from the hostpath-storage add-on,
the class is called microk8s-hostpath. The docs explicitly warn it's
not fit for production.
k0s
Another single-binary cluster. Version v1.36.4+k0s.0 from 2026-08-31, the companion k0sctl v0.32.2 from 2026-07-28. Linux only, with systemd or OpenRC, on amd64, arm64 and armv7. No macOS version.
The official instructions install k0s in one line: download a script and
pipe it straight to the shell under sudo. We do the same thing in two
steps: first download the script and see what it does, then run it. A
script that came from the network shouldn't go straight to sudo.
curl --proto '=https' --tlsv1.2 -sSf -o get-k0s.sh https://get.k0s.sh
less get-k0s.sh
sudo sh get-k0s.sh
sudo k0s install controller --single --start
sudo k0s status
sudo k0s kubectl get nodes
Multi-node is set up through k0sctl over SSH: k0sctl init > k0sctl.yaml, edit spec.hosts, then k0sctl apply --config k0sctl.yaml and k0sctl kubeconfig > kubeconfig. You describe the
machines in one file, k0sctl handles the rollout itself.
Who it's for: people who need a real cluster on a VPS in a single binary, without a container wrapper and without kubeadm's complexity. StorageClass needs to be installed separately.
Talos Linux
Version v1.13.9 from 2026-08-19. This isn't a package you install into an
existing system, it's a whole operating system built for Kubernetes.
Immutable: no SSH, no shell, no package manager. All management goes
through a gRPC API and the talosctl utility.
You can try it out locally, in containers:
brew install siderolabs/tap/talosctl
talosctl cluster create docker
kubectl get nodes -o wide
talosctl cluster destroy
The docker provider has flags --controlplanes, --workers,
--cpus-controlplanes, --memory-controlplanes, --kubernetes-version,
-p/--exposed-ports, --name and --cidr. Needs Docker 18.03 or newer.
On Linux you need sudo modprobe br_netfilter before starting, without
it Flannel crashes.
Limitation of the container mode: the upgrade and reset API calls
don't work in it. Exactly the operations Talos is used for in
production can't be tested locally in containers.
Who it's for: people studying immutable infrastructure who want a cluster that can't be fixed by hand over ssh. Not for someone new to Kubernetes: without a shell, the usual ways to see what's happening on a node disappear.
vcluster
Version v0.36.1 from 2026-08-03. This isn't a way to bring up a cluster from scratch. You need an already running host cluster (kind, k3d, k3s), inside which, in a separate namespace, its own API server and control plane come up, with nodes synced from the host.
brew install loft-sh/tap/vcluster
vcluster create my-vcluster --namespace team-x
vcluster delete my-vcluster --namespace team-x
Who it's for: multi-tenancy, isolated environments for CI, handing out "their own clusters" to several teams on top of one physical cluster. For one person learning on their laptop, it's an extra layer.
kubeadm
The official cluster bootstrap tool, part of Kubernetes v1.37.0. Installs only on Linux from deb or rpm. Requirements: 2 GiB RAM per machine, 2 CPU on the control plane, swap off, unique hostnames, MAC addresses and product_uuid.
kubeadm init --pod-network-cidr=<CIDR>
mkdir -p $HOME/.kube && sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config && sudo chown $(id -u):$(id -g) $HOME/.kube/config
kubectl apply -f <cni-manifest>
kubeadm join <host>:6443 --token <token> --discovery-token-ca-cert-hash sha256:<hash>
In place of <CIDR> you put the address range for Pods that your chosen
CNI expects. Pay attention to the third line. CNI isn't included, you
have to pick and install the network yourself. Until then, nodes stay
NotReady and Pods don't start. This is exactly the step most people
running kubeadm init for the first time trip over.
Why bother with it at all. kubeadm doesn't hide the makeup of the control
plane: you see /etc/kubernetes/manifests with your own eyes, the
static Pods for apiserver, controller-manager, scheduler and etcd,
certificates, join tokens. k3s and MicroK8s pack all of this into one
process, and understanding what the cluster is made of doesn't come from
that. For CKA prep, this is required experience.
For a quick local start, this is the worst option on the list. A separate Linux machine, manually turning off swap, installing CNI separately, StorageClass separately too. Half an hour of work where kind manages in thirty seconds.
You already have kubeadm: inside kind
kind builds every node with that same kubeadm. The binary sits right in the node container, and its version matches the Kubernetes version:
docker exec <cluster-name>-control-plane kubeadm version -o short
v1.35.0
That's why you can take apart the kubeadm-based control plane without a
separate server. The static Pod directory on a kind node is the same as
on a server after kubeadm init, and the component flags are read from
the same manifests. Where exactly kubelet looks for these files is
recorded in its config, in the staticPodPath field:
docker exec <cluster-name>-control-plane grep staticPodPath /var/lib/kubelet/config.yaml
kind sets its own network ranges. The kubeadm reference names
10.96.0.0/12 as the default for --service-cidr, but in a specific
cluster, only the kube-apiserver manifest tells the truth
(--service-cluster-ip-range). kube-controller-manager knows the Pod
range (--cluster-cidr).
What to choose
Need an always-on cluster on a VPS with a public address and minimal fuss: k3s. One curl, Traefik and StorageClass are already inside.
Need a cluster of several real machines, a home rack of mini PCs, and want to add components with one command: MicroK8s. Joining a second node is easier there than anywhere else.
Need a cluster on a VPS without a container wrapper, and want to describe all the machines in one file. Then k0s with k0sctl.
Studying immutable infrastructure and want to understand how nodes live without ssh: Talos.
Need to hand out several isolated clusters on top of one: vcluster.
Prepping for CKA or want to understand what the control plane is made of: kubeadm, and only for that reason.
Common mistakes
- Didn't install a CNI after
kubeadm init. Nodes sit in NotReady, CoreDNS in Pending, and it looks like the whole cluster is broken. - Didn't open the ports in firewalld or ufw. The k3s agent can't connect to 6443, and Pods on different nodes can't see each other without UDP 8472.
- Cloned a VM and didn't change the hostname. Nodes with the same name overwrite each other.
- Created a second LoadBalancer in k3s on an already-taken port.
svclb-…Pods sit in Pending,EXTERNAL-IPstays<pending>. Checkkubectl -n kube-system get podsand the events on that Pod. - Made your own StorageClass default in k3s, and after a restart
local-pathbecame default again. - Followed an old MicroK8s tutorial with
microk8s enable dashboard: that add-on is gone in 1.36. - Deleted the kube-apiserver Pod through kubectl and were surprised it came back. A static Pod lives as long as its file sits on the node.
What you'll need for the tasks
All three labs are heavy: a cluster on your laptop, from 1.5 to 3 GB of Docker memory depending on the lab. You need Docker 20.10+, kind v0.31.0 or newer, kubectl and curl. The second lab also needs k3d.
- Lab 1 (Investigate): the "You already have kubeadm: inside kind"
section, the static Pod concept,
docker exec,ls,grep,kubectl -n kube-system get pods,kubectl create configmap --from-literal. - Lab 2 (Fix): the "What k3s brings with it" section about ServiceLB,
kubectl get svc,kubectl -n kube-system get pods,kubectl describe pod,kubectl edit svc. - Lab 3 (Module mini-project): everything from the previous lessons. A
separate kubeconfig and namespace in the context (the kubectl lesson),
a kind config with a worker node and
extraPortMappings(the kind lesson), a PVC on the default StorageClass (the add-ons lesson), a Service of type NodePort.
Done reading?
Mark the lesson as read to move your track progress forward.
Lesson marked as done.
Go through it with an AI agent
The agent explains anything unclear in plain words, gives you 3 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.
Tell the agent: use MCP izi, give me lesson k8s-local-08-beyond-laptop
Progress saved in this browser
Clear your history or open this on your phone, and the marks will be gone. Leave your email and they'll move to your account. No password needed.
Done. moved to your account. You can see your progress on any device. My progress