Pets vs Cattle: Why I Run Kubernetes on a Single Laptop
There's a question that comes up in every homelab forum, every Reddit thread, every Discord server where someone mentions they're running Kubernetes at home:
"Why don't you just use Docker Compose?"
It's a fair question. I run a single Dell laptop with 16 GB of RAM. One node. No high availability. No multi-region failover. Docker Compose would work fine for what I'm doing: running a handful of self-hosted apps behind a reverse proxy.
So why Kubernetes?
The answer has nothing to do with whether Kubernetes is "better" than Docker Compose. It has everything to do with what I'm trying to build. And I don't mean the cluster.
The Pets vs Cattle Mental Model
If you've been in infrastructure long enough, you've heard the pets vs cattle analogy. It comes from a 2011 talk by Bill Baker at Microsoft, and it goes like this:
Pets are servers you name, nurture, and nurse back to health when they get sick. When a pet goes down, everyone notices. You SSH in, tail the logs, restart the process, and hope it stays up. Your Docker Compose stack on a VPS? That's a pet. You know its hostname. You know where the config files live. You've hand-edited docker-compose.yml enough times that you could probably recite the port mappings from memory.
Cattle are workloads you number, not name. When one goes down, you replace it. You don't SSH into a Kubernetes pod. You don't nurse it back to health. You declare what you want, and the system converges to that state. If a pod crashes, the scheduler restarts it. If a node dies, workloads move. The individual instance doesn't matter. The desired state does.
Now, on a single-node homelab, the cattle analogy breaks down a bit. I don't have spare nodes for workloads to migrate to. I'm not running a service mesh or horizontal pod autoscalers. But the mental model still applies, because cattle thinking changes how you build things, even at small scale.
What Changes When You Think in Desired State
Here's what a typical Docker Compose workflow looks like:
- SSH into the server
- Edit
docker-compose.yml docker compose up -d- Check if it worked with
docker compose logs - Realize you forgot to update the .env file
- Edit, restart, check again
- Hope you remember what you changed when something breaks next month
Here's the Kubernetes + GitOps equivalent:
- Edit a YAML manifest in a Git repository
git commit && git push- Flux detects the change and reconciles the cluster
- The cluster converges to the declared state
- If something's wrong,
git revertand push again
The difference isn't just tooling. It's a fundamentally different relationship with your infrastructure. Docker Compose is imperative: you tell it what to do. Kubernetes is declarative: you tell it what you want. And when that declarative state lives in Git, you get something Docker Compose can't give you: a complete, auditable, revertible history of every change you've ever made to your infrastructure.
What Kubernetes Gives a Single-Node Homelab
Even on one node, Kubernetes brings capabilities that Docker Compose simply doesn't have:
GitOps with Flux. Every change to my cluster is a Git commit. I don't SSH into anything. I don't run kubectl apply manually. Flux watches the repo and reconciles every 10 minutes. If I want to roll back a change, I revert the commit. The cluster state and the Git state are always in sync.
Automatic TLS with cert-manager. Every internal service gets a TLS certificate automatically. I define a ClusterIssuer, annotate my Ingress resources, and cert-manager handles the rest: issuing, renewing, and injecting certificates. No manual cert generation. No expired certificates at 2 AM.
Real ingress routing with Traefik. Not "nginx reverse proxy with a bunch of location blocks": actual Ingress resources with middleware chains for authentication, HTTPS scheme enforcement, and security headers. Adding a new service means adding an Ingress manifest, not editing a shared nginx config.
Network policies. Every namespace in my cluster has default-deny ingress and egress. Traffic is only allowed via explicit NetworkPolicy rules. My RSS reader can't talk to my password manager. My recipe app can reach the internet (to scrape recipes) but my budget tracker can't. This isn't possible with Docker Compose networking without significant manual iptables work.
Health probes and self-healing. Every workload has liveness and readiness probes. If a pod fails its health check, Kubernetes restarts it automatically. No cron job checking if containers are running. No restart: unless-stopped hoping for the best. Actual health-aware orchestration.
Resource limits. Every container has CPU and memory requests and limits. I know exactly how much RAM my cluster can allocate. I know which workloads are the heaviest. I can right-size everything because Kubernetes enforces the boundaries. Docker Compose has resource limits too, but they're rarely used in homelab setups because there's no scheduler making decisions based on them.
Secrets management. My secrets are SOPS-encrypted in Git using age keys. Flux decrypts them at reconciliation time. I can see that a secret exists in my repo without seeing its value. I can rotate secrets by updating the encrypted file and pushing. No .env files living unencrypted on a VPS.
RBAC. Kubernetes has built-in role-based access control. Service accounts, cluster roles, and bindings are all declarative. This matters less in a single-user homelab, but the patterns transfer directly to enterprise clusters where RBAC is non-negotiable.
The Honest Downsides
I'd be dishonest if I didn't acknowledge what Kubernetes costs on a single node.
RAM overhead is real. Before I deploy a single application, Kubernetes itself (kube-apiserver, etcd, controller-manager, scheduler), Cilium (CNI), and Longhorn (storage) consume roughly 3-4 GB of RAM. On a 16 GB machine, that's 25% of your resources gone before you've done anything useful. Docker Compose overhead? Effectively zero.
The complexity cliff is steep. Docker Compose has a learning curve measured in hours. Kubernetes has a learning curve measured in months. The first time you see a pod stuck in CrashLoopBackOff and you need to check events, describe the pod, read the logs, check resource limits, verify the image tag, inspect the security context, and review network policies, you'll wonder why you didn't just use Compose.
Debugging is harder. docker compose logs myapp vs kubectl logs -n myapp-namespace deploy/myapp --previous -c myapp. The Kubernetes command is more powerful, but it's also more verbose, and you need to know which namespace, which deployment, which container, and whether you want current or previous logs.
"Just restart it" becomes an investigation. In Docker Compose, docker compose restart myapp and you're done. In Kubernetes, you need to understand why the pod isn't healthy. Is it an OOMKill? A failed readiness probe? A missing secret? A network policy blocking egress? The answer is always more educational, but it's also always more time-consuming.
When Docker Compose Is the Right Answer
There are workloads where Kubernetes genuinely doesn't make sense, even for someone running a K8s homelab:
- Media servers: Plex, Jellyfin, and similar apps that need direct access to large media libraries on local storage. The volume management overhead isn't worth it.
- One-off tools: Something you spin up for an afternoon and tear down.
docker runis faster than writing a deployment manifest. - Anything where you don't care about the learning: If the point is just to have the service running and you don't want to learn from operating it, Docker Compose is genuinely simpler.
I still run Docker Compose on my Unraid NAS for media workloads. Kubernetes is for the services where I want the operational discipline, and the learning that comes with it.
The Real Reason
Here's the thing nobody says out loud in homelab communities: a homelab that teaches you Docker Compose teaches you Docker Compose. A homelab that teaches you Kubernetes, GitOps, Cilium, cert-manager, network policies, SOPS encryption, and Flux reconciliation teaches you the production stack.
I work in enterprise IT infrastructure. The skills I build operating this single-node cluster transfer directly to my day job. When I debug a network policy issue in my homelab, I'm building the same muscle memory I use when reviewing Cilium policies at work. When I write a HelmRelease with resource limits and health probes, I'm practicing the same patterns I'd use in a production cluster.
Docker Compose doesn't give you that. It's a perfectly good tool for running containers, but it doesn't teach you how production infrastructure works. And for me, that's the whole point.
The Thesis
The point isn't that Kubernetes is better than Docker Compose. For a single-node homelab running a handful of apps, Docker Compose is objectively simpler, lighter, and faster to set up.
The point is that the homelab exists to make you better. Not to serve web pages with minimal overhead. Not to maximize the number of services per gigabyte of RAM. The homelab is a learning environment, and the value of that environment is proportional to how closely it mirrors the systems you'll operate professionally.
I chose Kubernetes because I wanted a homelab that would make me a better infrastructure engineer. Every CrashLoopBackOff, every network policy debugging session, every SOPS encryption workflow: it's all practice for the real thing.
And honestly? The cluster runs just fine on a single laptop. The apps are fast. The GitOps workflow is cleaner than anything I ever had with Compose. The overhead is real but manageable. And every time I push a commit and watch Flux reconcile, I know exactly what changed, when it changed, and how to undo it.
That's worth 4 GB of RAM.
This post is part of a series documenting my homelab build on Talos Linux. Previous posts cover the platform layer (Cilium, Traefik, Longhorn), GitOps with Flux, observability, Cloudflare Tunnel, Authentik SSO, and Renovate. You can find the full series on the blog index.