← Learn··Updated 31 May 2026·8 min read

From Docker to k3s: scaling the homelab

When one Docker host stops being enough: what Kubernetes and k3s actually are, how Compose concepts map onto a cluster, and why most homelabs never need any of it.

Homelab
Build a homelab on DebianPart 8 of 8
#homelab
#kubernetes
#k3s
#ai-assisted

You spent the last seven parts of this series building something genuinely useful: a hardened Debian box running a handful of containers behind a reverse proxy, with backups and monitoring wired in. It works. It has worked for months. And then one evening you reboot that box to apply a kernel update, and every service you host goes dark for ninety seconds while it comes back up. That ninety seconds is the moment people start googling Kubernetes. This part is about whether that instinct is right — and most of the time, honestly, it isn't.

level:Advanced verified:Jun 2026

When one Docker host stops being enough

A single Docker host running Compose is a remarkably capable thing. The limit you eventually hit is not raw capacity — you can always buy more RAM — it is availability and lifecycle. There are a few specific failure modes that Compose on one machine cannot solve no matter how much hardware you throw at it.

The first is high availability. If the host goes down — a failed disk, a kernel panic, a reboot — every container goes down with it. Compose has no concept of "run this somewhere else instead". The second is rolling deploys. When you update an image, docker compose up -d stops the old container and starts the new one; there is a gap where the service is unreachable. The third is declarative reconciliation: Compose describes desired state, but nothing actively watches the running state and corrects drift. If a container crashes and the restart policy gives up, it stays dead until you notice.

Of those three, only high availability genuinely needs more than one machine — surviving a dead host means having another host to move work to. Rolling deploys and declarative reconciliation are things an orchestrator gives you, and k3s does both happily on a single node. So "move to k3s" and "run a multi-node cluster" are two separate decisions: you can adopt single-node k3s purely for the declarative, self-healing, rolling-deploy model and add nodes later — or never.

ℹ️ Note — A single-node k3s install is a complete, production-capable Kubernetes setup, not a half-measure. You get manifests, self-healing, rolling updates, ingress, and the whole ecosystem on one box; only true high availability — workloads surviving a node dying — requires a second machine. Plenty of real homelabs, and small production setups, run one node and never add another.
flowchart LR
  subgraph S1["Single host today"]
    A["Debian host"] --> B["Compose stack<br/><i>all services</i>"]
  end
  subgraph S2["Cluster later"]
    C["Node 1"] --> F["Scheduler<br/><i>places pods</i>"]
    D["Node 2"] --> F
    E["Node 3"] --> F
  end
  S1 -->|"reboot = full outage"| S2

One host means the reboot takes everything with it. A cluster lets the orchestrator move work to a surviving node.

ℹ️ Note "Node" just means a machine that runs workloads. In a homelab a node is usually a VM, not a separate physical box — more on that below.

What Kubernetes is

Kubernetes is a container orchestrator: a control loop that takes a declarative description of what you want running and continuously works to make reality match it. You hand it an object — "I want three copies of this image, reachable on this port" — and a set of controllers schedule those copies onto available nodes, restart them when they die, reschedule them when a node disappears, and roll them forward when you change the spec. It is the third generation of Google's internal Borg scheduler, rebuilt in the open.

The thing that makes Kubernetes feel heavy is that it is not one program. A real cluster runs an API server, a scheduler, a controller manager, an etcd datastore, and on every node a kubelet and a container runtime and a networking layer. That is a lot of moving parts to install, secure, and upgrade — which is exactly the problem k3s exists to solve.

Deployments, Services, and Ingress

You do not talk to Kubernetes in terms of containers. You talk to it in terms of higher-level objects, and three of them carry most of the weight:

  • A Deployment says how many copies of a workload should exist and which image they run. It owns the rolling-update logic — change the image tag and the Deployment brings up new pods before tearing down old ones, so there is no gap. (The actual containers live inside pods, the smallest schedulable unit.)
  • A Service is a stable internal name and virtual IP for a set of pods. Pods are ephemeral and their IPs change; a Service gives them one address that load-balances across whichever pods are currently healthy.
  • An Ingress is the cluster's edge: it maps external hostnames and paths to Services, doing the job your reverse proxy did in part five — but as a cluster-wide, declarative resource instead of a hand-edited config file.

If those three sound familiar, it is because you already wrote crude versions of them in Compose. That mapping is worth making explicit.

flowchart LR
  A["Compose service<br/><i>image + replicas</i>"] -->|"becomes"| B["Deployment"]
  C["depends_on / network<br/><i>service name DNS</i>"] -->|"becomes"| D["Service"]
  E["reverse-proxy labels<br/><i>host routing</i>"] -->|"becomes"| F["Ingress"]
  G["volumes:"] -->|"becomes"| H["PersistentVolumeClaim"]

Most Compose concepts have a direct Kubernetes counterpart — the model is the same, the vocabulary and the indirection are heavier.

What k3s is

k3s is a fully compliant Kubernetes distribution packaged as a single binary under 70 MB, built by Rancher (now SUSE) for edge, IoT, and exactly the homelab case in this series. It does not strip features out of Kubernetes — the API is the real thing — it strips out operational weight. Operation of all control-plane components is encapsulated in a single binary and process, and several things that you would otherwise install and wire up by hand come bundled.

Specifically, that one binary ships the containerd runtime, the Flannel CNI for pod networking, a Traefik ingress controller, a Klipper service load-balancer, and local-path storage. For a single-server install it even uses SQLite as the default datastore instead of standing up an etcd cluster — which is the single biggest reason a standard Kubernetes install is painful and a k3s one is not.

The install, and the two roles

The famous part is genuinely this short. The project ships an install script at https://get.k3s.io that downloads the binary and registers it as a systemd service:

# On the first machine — becomes the control plane (a "server")
curl -sfL https://get.k3s.io | sh -

# Grab the join token it generated
sudo cat /var/lib/rancher/k3s/server/node-token

# On each additional machine — joins as a worker ("agent")
curl -sfL https://get.k3s.io | \
  K3S_URL=https://<server-ip>:6443 \
  K3S_TOKEN=<token-from-above> sh -

That is the whole architecture in two commands. The same binary becomes a control-plane node or joins an existing cluster as a worker depending on the environment variables you pass it. The script also installs kubectl and writes a kubeconfig to /etc/rancher/k3s/k3s.yaml, so you can immediately run kubectl get nodes and watch the agents register.

flowchart TD
  A["k3s server<br/><i>API + scheduler + datastore</i>"] --> B["Agent node 1<br/><i>kubelet + containerd</i>"]
  A --> C["Agent node 2"]
  A --> D["Agent node 3"]
  E["kubectl<br/><i>your laptop</i>"] -->|"talks to :6443"| A
  B -->|"websocket to :6443"| A

One server holds the control plane and datastore; agents connect back to it over a websocket and run the actual workloads. Agents register with a websocket connection to the server's API on port 6443.

💡 Tip Run the server with --disable traefik if you want to bring your own ingress controller. The curl ... | sh -s - --disable traefik form passes flags straight to the installer.

Where do the nodes come from? Proxmox

A cluster needs more than one machine, and most people do not own three spare servers. The standard homelab answer is Proxmox VE, an open-source virtualization platform that tightly integrates the KVM hypervisor and LXC containers behind a web UI. It lets you consolidate everything onto one physical server and carve it into as many isolated virtual machines as the hardware can support.

In practice you install Proxmox on the metal, build one Debian VM, configure it, turn it into a template, and clone it into three identical VMs in minutes. Those three VMs become your k3s server and agents. This is the layer that gives you "multiple nodes" without multiple machines — though it is worth being clear-eyed that three VMs on one physical host do not give you real hardware high availability. If the Proxmox box dies, all three VMs die together. You get rolling deploys, declarative config, and self-healing against software failures; you do not get protection against the single physical host failing until you add a second physical host.

⚠️ Warning k3s is a large step up in operational complexity, and the overwhelming majority of homelabs never need it. You are trading a 200-line Compose file you can read in one sitting for a distributed system with its own networking, storage, certificate rotation, and upgrade model. Docker Compose on a single well-backed-up host is the right answer for a long, long time — often forever. Reach for k3s because you have a concrete problem it solves, not because the diagrams look impressive.
Danger Piping a remote script straight into a shell with curl ... | sh runs arbitrary code as root before you have read a line of it. The k3s installer is widely used and reputable, but the habit is dangerous in general. Read the script first, or pin to a checksum-verified release, especially on a host that holds your data.

What you actually gain, concretely

It is easy to lose the thread in vocabulary, so here is the payoff in plain terms. With a k3s cluster across three Proxmox VMs:

  • Rebooting a node to patch it no longer takes your services down — the scheduler moves the pods to a surviving node first, then drains the one you are patching. The ninety-second outage from the intro disappears.
  • Deploying a new image is a kubectl apply (or a Git commit, if you adopt GitOps later) that rolls pods forward with no downtime, and rolls back automatically if the new pods fail their health checks.
  • The whole cluster's desired state lives in version-controlled YAML. There is no "what did I configure on that box eight months ago" — the manifests are the configuration, and the cluster continuously reconciles toward them.

That last point is the real reason engineers fall for Kubernetes: it turns infrastructure into reviewable, diffable, declarative text. Whether that is worth the complexity tax for your three services is a genuine question, and "no" is a perfectly good answer.

A short close

This is where the series points at the door rather than walking you through it, because the honest advice is to stay on Compose until something actually hurts. But when you do cross over, the path is the one this part sketched: Proxmox to carve the nodes, the get.k3s.io script to stand up a real Kubernetes cluster in two commands, and the Compose mental model — service, dependency, proxy — mapping cleanly onto Deployments, Services, and Ingress.

This site is not hypothetical about any of it. It runs on K3s on Hetzner, so the trade-offs in this post are the ones I live with rather than ones I read about. If you want the smaller story behind the bigger name, why Kubernetes is called k8s is the etymology. And if you arrived here from the start, the foundation you are scaling up from is Docker and Compose on Debian — the series hub has the whole path from a bare Debian install to this point.

Ready to actually cross over rather than just read about it? The Kubernetes from scratch with k3s series picks up exactly here and teaches the concepts hands-on — clusters and nodes, pods, deployments, services, ingress, storage — on a real k3s cluster you build yourself.