Sign in
← Kubernetes locally
9 min · medium · Lesson 8 of 8

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.

OptionRuns onSecond nodeWhat's already inside
k3sLinux onlyK3S_URL and K3S_TOKENTraefik, ServiceLB, local-path
MicroK8sLinux directly, macOS and Windows through a VMmicrok8s add-node, HA with three nodesadd-ons with one command
k0sLinux onlyk0sctl over SSHStorageClass installed separately
Talosits own OS, no SSH or shell--controlplanes, --workersmanaged only through talosctl
vclusteron top of an existing clusternodes come from the hostits own control plane in a namespace
kubeadmLinux onlykubeadm joinCNI 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

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.

Done reading?

Mark the lesson as read to move your track progress forward.

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.

Get access for $49

Tell the agent: use MCP izi, give me lesson k8s-local-08-beyond-laptop