You have mastered Docker Compose. Your homelab runs a dozen stacks behind Traefik, your backups are automated, and monitoring covers every container. But managing twenty individual Compose files across multiple hosts is starting to hurt. Rolling out an update means SSH into the right VM, pulling the right stack, and praying nothing conflicts.
K3s — Rancher’s CNCF-certified lightweight Kubernetes distribution — solves this. It strips Kubernetes down to a single binary under 100 MB, replaces etcd with SQLite (or embedded etcd for HA), and runs comfortably on 1 GB RAM VMs. It is Kubernetes, not a toy: every standard kubectl command, Helm chart, and Kubernetes resource works as expected.
This guide walks through deploying a production-grade K3s cluster on Proxmox VMs, adding longhorn for persistent storage, MetalLB for bare-metal load balancing, Traefik for ingress, and migrating your Docker Compose workloads into Kubernetes.
Why K3s Over Full Kubernetes or Docker Swarm
Full Kubernetes (kubeadm, kube-spawn) assumes three control plane nodes with etcd, which wastes resources in a homelab. Docker Swarm is simpler but lacks the ecosystem — no Helm, no CRDs, limited CNI choices.
K3s hits the sweet spot: it speaks real Kubernetes API, supports every standard workload, and runs on a single VM if that’s all you have. Rancher reports over 200,000 production K3s deployments in edge and IoT environments. If it runs factory robots and oil rig sensors, it handles your homelab.
Resource comparison for a three-node cluster:
| Platform | RAM per node | Disk | Binary size |
|---|---|---|---|
| kubeadm (etcd) | 2–4 GB | 20 GB+ | 500+ MB |
| K3s (SQLite) | 512 MB–1 GB | 10 GB | ~90 MB |
| K3s (embedded etcd, HA) | 1–2 GB | 15 GB | ~90 MB |
For a homelab, a three-node cluster with embedded etcd (HA) or a single server with SQLite plus two agents (agents only) is the sweet spot.
Prerequisites and VM Provisioning on Proxmox
Start with three Proxmox VMs running Ubuntu Server 24.04 LTS (minimal install, no Docker — K3s bundles containerd):
| Node | Role | vCPU | RAM | Disk |
|---|---|---|---|---|
| k3s-srv1 | Server (control plane) | 2 | 2 GB | 20 GB |
| k3s-agent1 | Agent (worker) | 2 | 4 GB | 40 GB |
| k3s-agent2 | Agent (worker) | 2 | 4 GB | 40 GB |
Create them with cloud-init or manually. The minimum viable setup is a single server and one agent, but three nodes give you tolerance for Longhorn replication.
Network Setup
Assign static IPs on your management VLAN. These examples use the
10.0.30.x/24 subnet:
k3s-srv1: 10.0.30.10
k3s-agent1: 10.0.30.11
k3s-agent2: 10.0.30.12
Enable promiscuous mode on the Proxmox vNIC if you plan to use MetalLB in L2 mode — K3s nodes will respond to ARP for service IPs on that interface.
|
|
Installing the K3s Server Node
SSH into k3s-srv1 and run the one-line install:
|
|
Flags explained:
--cluster-init— Start the cluster with embedded etcd (HA mode). Omit for single-node SQLite mode.--disable=traefik— We install Traefik ourselves via Helm for full control. K3s bundles a minimal Traefik by default.--disable=servicelb— Disable K3s’s built-in LoadBalancer. We use MetalLB instead.--node-ip— Pin the node IP so cluster traffic uses the correct interface.--flannel-iface— Tell Flannel (the CNI) which interface to use for pod networking.
After the install completes, copy the node token — you need it for agent nodes:
|
|
Save this token. Also copy the kubeconfig for local access:
|
|
You should see k3s-srv1 in Ready status.
Joining Agent Nodes
On k3s-agent1 and k3s-agent2, run the agent install with the
server’s IP and token:
|
|
Repeat for agent2 with --node-ip=10.0.30.12.
Back on the server, verify all nodes joined:
|
|
Expect output like:
NAME STATUS ROLES AGE VERSION
k3s-srv1 Ready control-plane,master 5m v1.32.3+k3s1
k3s-agent1 Ready <none> 2m v1.32.3+k3s1
k3s-agent2 Ready <none> 2m v1.32.3+k3s1
Installing Helm
Helm is the Kubernetes package manager. Everything from Longhorn to Traefik installs via a single Helm command:
|
|
MetalLB — Bare-Metal Load Balancer
Kubernetes LoadBalancer services only work on cloud providers
(AWS, GCP) where a cloud controller provisions real load
balancers. In a homelab, MetalLB assigns IPs from your local
subnet and announces them via ARP (L2 mode) or BGP.
Install MetalLB
|
|
Wait for the pods to start:
|
|
Configure an IP Address Pool
Create metallb-pool.yaml:
|
|
Apply it:
|
|
Any service with type: LoadBalancer now gets an IP from the
10.0.30.200-10.0.30.220 range without needing a real load
balancer.
Longhorn — Distributed Persistent Storage
Docker Compose volumes map to host directories. Kubernetes
requires CSI (Container Storage Interface) drivers for persistent
storage. Longhorn creates replicated block storage across your
K3s nodes using the available disk space on each node’s
/var/lib/longhorn/ directory.
Install Longhorn
|
|
With defaultReplicaCount=2, Longhorn stores two copies of every
volume — enough redundancy for a three-node homelab without
wasting the disk space of three replicas.
Wait for all pods:
|
|
Access the Longhorn UI at http://10.0.30.10:30000 (it spawns as
a NodePort service by default). Every node’s available disk is
automatically detected and added as storage.
Create a StorageClass for Workloads
Longhorn creates a default longhorn StorageClass. Verify it:
|
|
Output:
NAME PROVISIONER RECLAIMPOLICY
longhorn (default) driver.longhorn.io Delete
longhorn-static driver.longhorn.io Delete
Your PVCs (PersistentVolumeClaims) now automatically provision replicated block storage across the cluster.
Traefik Ingress Controller
Traefik is the standard ingress for K3s homelabs. It receives external HTTP/HTTPS traffic, routes it to the correct service based on hostname, and handles TLS termination.
Install Traefik with Helm:
|
|
Check that Traefik received a MetalLB IP:
|
|
Example output:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
traefik LoadBalancer 10.43.231.45 10.0.30.200 80:31234,443:32567
Point *.apps.yourdomain.com DNS to 10.0.30.200 and Traefik
routes all subdomain traffic to the matching Ingress resources.
Deploying Your First Workload
Let’s deploy Whoami — a simple HTTP echo service — to verify everything works end-to-end.
Create a Deployment and Service
|
|
Apply it:
|
|
Wait a moment, then curl the ingress:
|
|
You should see the container’s hostname, IP, and request headers.
Adding TLS with Let’s Encrypt
Create a ClusterIssuer for automatic certificates:
|
|
Install cert-manager first:
|
|
Then annotate any Ingress to get automatic TLS:
|
|
cert-manager handles the HTTP-01 challenge automatically, stores
the certificate in whoami-tls, and Traefik serves it.
Migrating from Docker Compose to Kubernetes
Moving existing Compose workloads to K3s follows a pattern:
- Images — Your existing Docker images work as-is. No rebuild needed.
- Volumes — Replace bind mounts with PVCs.
- Networks — Kubernetes Services replace Compose network
names. DNS resolves as
<service>.<namespace>.svc.cluster.local. - Environment — Use ConfigMaps (non-secret) and Secrets.
- Reverse proxy — Replace Traefik Compose labels with Ingress resources + annotations.
Example: PostgreSQL from Compose to K3s
Docker Compose volume:
|
|
Kubernetes equivalent:
|
|
A tool like kompose (https://kompose.io/) can auto-translate
Compose files into Kubernetes manifests as a starting point, but
reviewing and hand-tuning the output is recommended for
production workloads.
Monitoring the Cluster
Install the kube-prometheus-stack for real-time cluster visibility:
|
|
Access Grafana:
|
|
Open http://localhost:3000 — default credentials are
admin/prom-operator. The “Kubernetes / Views / Global” and
“Nodes” dashboards give immediate insight into pod resource usage,
node health, and cluster events.
Longhorn also exposes Prometheus metrics at
http://<any-node-ip>:9500/metrics if you want to scrape storage
metrics into the same Prometheus instance.
What’s Next
With a working K3s cluster, your homelab gains real Kubernetes capabilities:
- Namespace isolation — Separate environments
(
staging,prod,monitoring) with resource quotas. - GitOps — Connect a GitHub/Gitea repo with ArgoCD or Flux for declarative deployments.
- Horizontal pod autoscaling — Let Kubernetes scale web services based on CPU or custom metrics.
- Cluster upgrades — K3s supports one-line upgrades:
curl -sfL https://get.k3s.io | sh -s - --update-server.
K3s is Kubernetes without the baggage — small enough for a Raspberry Pi, capable enough for production edge workloads. Your homelab deserves the upgrade.