Skip to main content

k3s vs Kubernetes vs MicroK8s: the 4x RAM Gap Is Real — and Where a Lightweight Distro Stops Being a Fleet

10 min readDora NodaDora Noda
Share
On this page

A k3s agent node needs 512 MB of RAM. A stock kubeadm worker needs 2 GB. That 4x gap is real, documented in both projects' official requirements, and current as of the August 2026 comparisons — and it is the least interesting difference between them. The interesting difference shows up the day your second machine arrives, when you discover that everything a lightweight distro saved you was inside the cluster, and everything it never promised you lives outside it.

First, the parity check that makes the comparison honest. k3s is not a cut-down Kubernetes-shaped thing: its August 4, 2026 release lines, v1.35.7+k3s1 and v1.34.10+k3s1, each embed the full upstream Kubernetes release of the same number, work with standard kubectl and Helm, and pass CNCF conformance. (k3s has since shipped v1.37.0+k3s1 in mid-September, tracking upstream's August 26 v1.37 release within weeks.) So the RAM gap below is an apples-to-apples number: the same Kubernetes API, the same manifests, roughly a quarter of the worker-side memory.

With one caveat, stated before the table rather than after it: the 4x gap is worker-side. A k3s server node wants the same 2 GB a kubeadm control-plane machine wants. On a single box that runs both the control plane and your tenants, the gap narrows — and that is exactly why the single-box math deserves its own section instead of a slogan.

The full RAM table​

All numbers below are the projects' own published minimums, as compiled in the August 2026 three-way comparison and checked against each project's docs:

kubeadm (stock Kubernetes)k3sMicroK8s
Control/server node RAM2 GB minimum2 GB minimum540 MB floor; 4 GB recommended
Worker/agent node RAM2 GB minimum, regardless of role512 MBNo separate role by default
Control node CPU2 vCPU2 vCPUNot published separately
Packaged asDIY assemblySingle binary under 100 MBSnap + add-on system
Ingress / CNI / storageBring your ownTraefik, Flannel, local-path storage bundledOptional add-ons (microk8s enable ingress storage …)
Datastoreetcd (you run it)SQLite via Kine by default; etcd for HAdqlite, HA automatic at 3+ nodes

Two rows outside the minimums matter more than the minimums. Real kubeadm control planes typically run 4 to 8 GB of RAM, not the 2 GB floor. And MicroK8s' 540 MB figure is the whole cluster on one node, a different shape than k3s' split server/agent numbers — Canonical's own guidance says to budget 4 GB of RAM and 20 GB of disk for anything beyond smoke tests.

k3s' headline trick is architectural: a shim called Kine translates the etcd API into SQL, so a single-server cluster runs on SQLite instead of a three-member etcd quorum. That plus the single-binary packaging (API server, scheduler, controller manager, kubelet, and the bundled add-ons in one file under 100 MB) is where the tagline comes from: half the memory. MicroK8s takes the opposite packaging bet — a snap with a plugin system — and trades k3s' release speed for version tracks you can pin per minor release, with as many as four tracks supported in parallel.

What the gap buys on one Hetzner box​

Put the numbers on a concrete machine. Hetzner's CX22 — 2 shared vCPUs, 4 GB of RAM, 40 GB of NVMe — costs roughly €4–5 a month. That box runs a k3s server plus a real tenant workload comfortably: the 2 GB server floor leaves half the machine for apps, and five minutes after curl -sfL https://get.k3s.io | sh - you have working ingress (Traefik), networking (Flannel), a LoadBalancer implementation (ServiceLB), DNS, and persistent storage. No CNI research, no ingress-controller bake-off, no storage-class YAML.

The same box with kubeadm is a different afternoon. The 2 GB floor is technically satisfiable, but a comfortable control plane realistically wants the next box up, and every layer the k3s installer handed you — CNI, ingress, storage, load-balancer addresses — is a separate decision, install, and upgrade stream you now own. None of that is hard in isolation. Together it is the difference between "deployed tenants today" and "still assembling the platform this week."

So the honest single-box story is two numbers, not one: the RAM gap saves roughly one box size (a €4 box instead of a box twice its memory), and the batteries-included gap saves the assembly week. For a team running its first tenants on owned hardware, the second saving is usually worth more than the first. Budget 2 vCPUs and 4 GB as the comfortable single-node baseline regardless of distro — the same baseline the September field guide recommends for a node you don't want to babysit.

MicroK8s' different bet​

MicroK8s deserves its own paragraph because it optimizes for a different buyer. Where k3s says "everything works in five minutes," MicroK8s says "nothing you didn't ask for, and the version you asked for." Capabilities arrive as add-ons (microk8s enable dns ingress storage metallb), and installs ride per-minor-version snap channels, so a team can sit on one Kubernetes minor for months while k3s users ride a cadence that lands new minors within weeks of upstream.

That conservatism extends to scaling up. MicroK8s clustering is genuinely pleasant: microk8s add-node on the existing cluster, join from the new machine, and at three or more nodes the dqlite datastore goes highly available on its own, promoting standby nodes automatically. No datastore migration, no bootstrap flag you had to know about on day one. If your shop is already Ubuntu-shaped and your compliance story says "pin the minor version," MicroK8s is the lightweight that fights you least.

The price is ecosystem shape: snap-based install (a friction point on non-Ubuntu hosts), a 4 GB recommended floor that erases most of the RAM advantage over a careful kubeadm build, and — the theme of the rest of this post — the same hard boundary k3s has. Joining a node is easy; the machine it joins on is still yours to provision, patch, and replace by hand.

The second machine arrives: one-way doors​

Everything above was machine one. Machine two is where lightweight distros collect their toll, and the tolls are specific enough to name.

k3s: SQLite is a one-way door. The SQLite default that makes single-server k3s so light cannot gain HA servers later. A highly available k3s control plane needs three servers on embedded etcd, and the first one must have been started with --cluster-init — a flag you either passed on day one or didn't. As one September 2026 field guide puts it: SQLite is perfect for one server and useless for HA, and "can't add a second server" traces back to a first server that wasn't started with --cluster-init. The fix is a reinstall of the control plane, planned during an outage window you didn't want.

Storage doesn't follow the pod. k3s' bundled local-path provisioner stores each volume on the node the pod happened to land on. The day a pod reschedules onto machine two, its data stays on machine one. Multi-node persistence means adopting shared storage — NFS, Ceph, Longhorn — which is a real subsystem with its own sizing, monitoring, and failure modes, not an installer flag. MicroK8s users hit the same wall: the default hostpath storage is single-node, and HA guides quietly assume you've already stood up something shared.

Every join is a snowflake. A k3s agent joins with a token from /var/lib/rancher/k3s/server/node-token; a MicroK8s node joins with an add-node token. Somebody provisions the VM (console click, Terraform apply, Ansible play), somebody SSHes in, somebody runs the installer with the right flags and the current token. That somebody is you, every time, in runbook order. It works fine at three nodes and becomes the job at thirty.

None of this is a bug. k3s and MicroK8s are cluster distributions: their reconcile loop starts at the Kubernetes API and stops there. The machines underneath are out of scope by design. Which is exactly the seam Cluster API exists to cover.

What "a fleet" actually means​

Cluster API inverts the model. Machines stop being pets you provision and become objects the platform reconciles — on Hetzner, via the CAPH provider, which manages Hetzner Cloud and bare-metal servers declaratively through the Kubernetes API. A management cluster runs the controllers; your fleet is YAML. The difference is sharpest in the three scenarios every platform eventually faces:

Scenariok3s / MicroK8s by handCluster API + CAPH
Dead node at 3 AMNode goes NotReady; you get paged, delete the Hetzner server, provision a replacement, install, join with a fresh tokenHealth check fails; controller deletes the dead server, provisions and bootstraps a replacement, joins it — no page
Kubernetes upgradeSSH to each node in the right order, rerun installers, hold your breath on etcd/dqlite quorumRolling machine replacement: new machines on the new version, old ones drained and deleted, declared in one manifest
Scale from 1 node to 5Repeat the provisioning runbook four timesChange replicas and let the controllers create four Hetzner servers

Notice what the right column never mentions: SSH, tokens, install scripts, quorum surgery. That is what "fleet-wide reconciliation" means — the desired state covers the machines, not just the pods. And notice what it costs: a management cluster to host the controllers, provider components to install and upgrade, and a bootstrap story (typically clusterctl) that is unambiguously heavier than curl | sh. Nobody should pretend otherwise.

There is a middle layer worth naming honestly: Terraform plus Ansible (or tools like k3sup) can provision the VMs and run the join commands repeatably. That gets you repeatable pets, not cattle. It removes the console clicking but not the fundamental shape — when a node dies at 3 AM, your Terraform still needs a human to notice and apply. Reconciliation without a human in the loop is the line, and only a controller on a reconcile loop crosses it.

The honest crossover​

So when does the lightweight stop being enough? Not at a node count — at an on-call burden. One k3s box serving early tenants, with --cluster-init etcd from day one and a shared-storage plan before machine two, is a genuinely good start. It is cheap, it is conformant, and every manifest you write ports forward unchanged. The decade of "don't run your own control plane" advice was about managed-vs-owned, not about which owned shape you pick first.

The crossover arrives with the second page: the first dead node that needed a human, the first upgrade you scheduled around quorum instead of around tenants, the first time "add capacity" meant an afternoon instead of a manifest edit. At that point the management-cluster overhead of Cluster API stops being a tax and starts being the cheapest way to buy back your nights — and because CAPH provisions the same Hetzner machines you'd click into existence by hand, scaling from one node to many never hands control-plane ownership to a managed vendor. The fleet stays yours; only the toil changes hands.

If you start k3s-first — and for a single box, you reasonably might — make three day-one decisions that keep the fleet door open. Start the first server with --cluster-init on embedded etcd even though you have one node. Plan shared storage before you need machine two, not during the outage that forces it. And pin your versions deliberately, because the manifests, Helm charts, and operator habits you build now are the payload the future fleet inherits. The 4x RAM gap got you started cheap. Deliberate day-one choices are what let you grow without reinstalling.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.

Related articles

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex