From Proxmox VMs to k3s Workloads: Designing a Practical Home Cloud

A homelab can begin as a spare machine under a desk and quietly become something more ambitious: a small, imperfect cloud built from hardware you can hear working. My stack is centered on Proxmox for virtualization and k3s for Kubernetes orchestration. The goal is not to imitate a hyperscaler feature for feature. It is to create a platform that makes it easy to deploy, break, observe, rebuild, and learn from real workloads.

The distinction matters. A homelab is most useful when it has enough operational friction to teach good habits, but not so much complexity that every project turns into infrastructure maintenance. Proxmox provides a stable foundation for virtual machines, while k3s provides a lightweight Kubernetes distribution that makes application deployment repeatable. Together, they create a useful boundary between physical infrastructure and the services running above it.

The two-layer model

I think about the environment as two separate platforms.

The first is the virtualization layer. Proxmox owns the physical hosts, networking bridges, virtual disks, VM lifecycle, snapshots, and backups. Its job is to turn a limited set of physical resources into flexible compute units. If I need to test a new Kubernetes node, experiment with another Linux distribution, or create an isolated utility server, I do not need another physical machine. I create a VM with deliberate CPU, memory, and disk limits.

The second is the application platform. k3s owns container scheduling, service discovery, ingress, workload health, configuration, and the day-to-day lifecycle of applications. It is where APIs, dashboards, databases, automation workers, and experiments live.

Keeping those concerns separate has practical benefits. A failed container deployment should not require thinking about VM disk images. A Kubernetes upgrade should not mean changing the Proxmox host configuration. And if a cluster node becomes unhealthy, the recovery path is clear: repair or replace the VM, then let Kubernetes restore the workloads.

This is not enterprise-grade separation in every sense. The same physical hardware may still support all layers. But the mental model is valuable. It gives each problem a home.

Why k3s fits the homelab

Full Kubernetes distributions are powerful, but they can be resource-heavy and operationally demanding for a small environment. k3s reduces that overhead without removing the Kubernetes APIs and concepts that matter most. It is a compact distribution designed for edge, IoT, development, and constrained environments, which makes it well suited to virtual machines with modest allocations.

The result is a cluster that still supports familiar Kubernetes workflows:

  • Deployments and StatefulSets for application lifecycle management
  • Services for stable internal networking
  • ConfigMaps and Secrets for configuration boundaries
  • Persistent volumes for stateful workloads
  • Namespaces for separating environments and concerns
  • Ingress resources for HTTP routing
  • Helm charts for packaging shared infrastructure

The point is not to run Kubernetes merely because it is popular. The point is to make deployments declarative. Instead of logging into a server and manually starting containers, I can describe the desired state in manifests or Helm values. Kubernetes continuously works toward that state.

That behavior changes how experimentation feels. A new service becomes a repository, an image, a deployment definition, and a route. Removing it is equally explicit. Infrastructure stops being a collection of undocumented shell history and becomes something closer to a system design.

Node design: small VMs with clear responsibilities

The cluster is composed of Linux virtual machines managed by Proxmox. At a minimum, I separate control-plane responsibilities from worker capacity. In a smaller setup, a server node can also schedule workloads, which is perfectly reasonable. As the environment grows, dedicated worker nodes make resource allocation easier to understand.

A typical layout might include:

  • k3s server nodes for the Kubernetes control plane and cluster state
  • worker nodes for general application workloads
  • utility VMs for services that do not belong in the cluster yet, such as backup tooling or a network appliance
  • Proxmox itself as the virtualization and recovery layer

The important design choice is not the exact node count. It is that each VM has an intentional purpose and a known resource budget. Overcommitting memory is easy in virtualization platforms, but it can make failure modes confusing. A Kubernetes pod may be evicted because its node is under pressure, while the real problem is that too many VMs are competing for RAM on the same physical host.

I prefer to leave headroom. Idle capacity is not wasted capacity in a learning environment; it is what allows maintenance, recovery, and sudden workload spikes to be survivable.

Networking: the path from the internet to a pod

Networking is where the stack becomes tangible. A request may begin outside the home network, pass through DNS and a router, arrive at an ingress controller, traverse a Kubernetes Service, and finally reach a pod. Each hop has a distinct responsibility.

Within the cluster, Kubernetes Services provide stable names and virtual IPs. Applications should communicate through service DNS names rather than depending on pod addresses, because pods are intentionally disposable.

At the edge, Traefik is a natural fit for k3s because it can act as the ingress controller. It watches Kubernetes Ingress resources and routes HTTP traffic based on hostnames and paths. A route such as app.example.net can be expressed as configuration alongside the application instead of as a manually maintained reverse-proxy file.

For external access, Cloudflare can provide DNS management, TLS support, and an additional layer between public traffic and the home connection. Whether services should be exposed publicly is a deliberate decision, not a default. Many internal tools are better kept behind a VPN or restricted to the local network.

The guiding principle is simple: expose as little as possible, and make every exposed path understandable. If I cannot explain how a request reaches a service, I do not yet have a maintainable network design.

Storage is where the easy answers end

Stateless applications are Kubernetes at its most elegant. Replace a pod and little changes. Stateful services are different. A database needs durable storage, a backup plan, upgrade discipline, and recovery testing.

For small workloads, k3s can use local storage provisioners that map persistent volume claims to node-local disks. This is convenient, but it introduces an important constraint: data is attached to a node. If a database pod is rescheduled elsewhere, its storage may not follow automatically.

That is not necessarily wrong for a homelab. It is a trade-off that should be explicit. For services such as PostgreSQL, Redis, or other stateful systems, I decide whether the learning goal is Kubernetes-native operation or dependable personal infrastructure. Sometimes a database belongs in the cluster. Sometimes it is better placed in a dedicated VM with simpler backup and restore procedures.

Proxmox snapshots are useful, but they are not a complete data-protection strategy. An application-consistent database backup is still necessary. Snapshots can help recover a broken VM; database dumps, point-in-time recovery, and tested restoration procedures protect the data itself.

Deployment as a repeatable path

A cluster becomes genuinely useful when code has a predictable route to it. My preferred pattern is to store application code, Dockerfiles, Kubernetes manifests, and deployment documentation in Git. A push can trigger GitHub Actions to build a container image, run tests, and publish the artifact.

From there, deployment should be intentional. For simple projects, applying updated manifests may be enough. For more mature workflows, a GitOps-style approach can reconcile the cluster against a repository containing the desired state.

The key idea is that the deployed version is traceable. I should be able to answer three questions quickly:

  1. Which image is running?
  2. Which commit produced that image?
  3. Which configuration was applied with it?

This discipline is useful even for a personal dashboard. It turns a deployment failure from an archaeological exercise into a diagnosable event.

Reliability means practicing recovery

The most revealing homelab exercise is not deploying something new. It is recovering something broken.

I treat failures as design prompts. What happens if a worker VM disappears? What happens if the Proxmox host needs maintenance? What happens if a persistent volume is unavailable? What happens if a bad image is deployed? The answers do not need to be perfect, but they should exist before an outage turns into a late-night investigation.

This is where Proxmox and k3s complement each other. Proxmox provides VM backups, snapshots, and hardware-level control. Kubernetes provides workload restarts, replicas, health probes, and declarative configuration. Neither replaces the other. Together, they offer layers of recovery appropriate for a small platform.

A platform that earns its complexity

The stack is not valuable because it contains Proxmox, k3s, Docker, Traefik, or Kubernetes. It is valuable because those tools create a repeatable environment for building software with real operational characteristics.

A project deployed here has DNS, TLS, logs, resource limits, container images, network policies to consider, and a recovery story to improve. It behaves less like an application launched from a laptop terminal and more like a service with responsibilities.

That is the real purpose of this homelab: not a miniature data center for its own sake, but a practical home cloud where every new workload teaches something about the one beneath it.

Comments (0)

No comments yet. Be the first to share your thoughts!

From Proxmox VMs to k3s Workloads: Designing a Practical Home Cloud