The Cost of Abstraction: Why Your Homelab Performance Dies at the Kernel
The Cost of Abstraction: Why Your Homelab Performance Dies at the Kernel
So, you’ve got some old hardware—maybe an old ThinkPad or a retired desktop—and you want to host a development environment for 2–3 users. You’re looking at Ubuntu + Multipass versus Proxmox VE because that old machine might have more power than the latest Raspberry Pi.
Here is the thing: most people look at “Ease of Use” and stop there. But as engineers, we have to look under the hood. We have to look at the kernel, the hypervisor, and how the hardware is actually being sliced. Because on old hardware, every context switch matters.
Recap: What is a Kernel?
The Kernel is the ultimate orchestrator, the “God mode” process that sits directly on the metal. Everything you write—your Java apps, your Python scripts—lives in User Space, a restricted sandbox. To do anything meaningful, like sending a TCP packet or writing to disk, you must “knock on the door” of the kernel via a System Call (syscall).
But here’s the catch: crossing that boundary isn’t free. This is the cost of abstraction. Every syscall triggers a Context Switch, where the CPU stops your code, saves state, and flips to Kernel Mode. This adds latency. The kernel manages the “lie” of Virtual Memory, ensuring your buggy app doesn’t crash the entire RAM. The kernel decides which process gets the CPU and for how long; it also manages the Receive Buffer. When a packet hits the NIC (Network Interface Controller), the kernel grabs it first before your application ever sees it.
- Examples: Linux kernel (Linux), XNU kernel (macOS), Windows NT (Windows).
Recap: What is a Hypervisor?
If the kernel is the “boss” that sits between your applications and the hardware—managing memory, CPU, and disk—then a hypervisor is essentially the boss of the bosses.
In a standard setup, you have one kernel that owns the hardware. But what if you want to run multiple different kernels (like Linux and Windows) on the same machine at the same time? That’s where the hypervisor comes in. While a kernel manages processes, a hypervisor manages entire operating systems. It tricks each kernel into thinking it has exclusive control of the hardware, when in reality, it’s only getting a “slice.”
- Examples: Hyper-V, VMware ESXi, KVM.
The “Double Kernel” Problem (Multipass)
Let’s talk about Multipass. Multipass is essentially a high-level orchestrator. It uses cloud-init to spin up Ubuntu instances, usually backed by QEMU/KVM.
Think about the architecture here:
- Hardware
- Host OS: Ubuntu Server (Kernel #1)
- Hypervisor: Multipass/QEMU
- Guest OS: Ubuntu Instance (Kernel #2)
When your user’s application wants to write to a disk or send a packet, it makes a syscall:
- The application calls the Guest Kernel.
- The Guest Kernel realizes it doesn’t own the hardware, so it triggers a VM-Exit.
- Control is handed back to the Host Hypervisor (running on the Host Kernel).
- The Host Kernel finally talks to the silicon.
On legacy hardware, these transitions (Ring 3 to Ring 0, then Guest to Host) are expensive. The CPU overhead of managing two full kernels just to let a user run a Node.js server is, quite frankly, a waste of resources.
The Bare Metal Approach (Proxmox VE)
Now, let’s look at Proxmox. Proxmox is a Type 1 Hypervisor. It is essentially a stripped-down Debian distribution that lives as close to the silicon as possible.
When you run a VM in Proxmox, you are still using KVM, but there is no “middleman” OS running a desktop environment or heavy background services. But the real “magic” for 2–3 users on old hardware isn’t VMs—it’s LXC.
LXC: The Performance King
This is where the engineering gets interesting. Proxmox supports LXC (Linux Containers). Unlike a VM, a container does not have its own kernel. It shares the Proxmox host kernel.
It uses two key features of the Linux Kernel:
- Namespaces: This provides the illusion of isolation (Process IDs, Network stacks, Mount points).
- Cgroups: This limits the resources (CPU, RAM, I/O) so one user doesn’t crash the server.
Because there is only one kernel, when a user in an LXC container makes a syscall, it goes directly to the host kernel. No VM-Exits. No hardware emulation. It’s almost as fast as running a process directly on the metal, but with the management benefits of a virtual environment.
The Networking Bottleneck
Another thing to consider is the Network Stack.
Multipass often relies on a virtual bridge and NAT. Every packet leaving that Multipass instance has to be processed by the guest network stack, then the host network stack, undergoing Address Translation.
Proxmox uses Linux Bridges natively. You can give each user their own internal IP on your LAN. The kernel simply switches the frames to the correct virtual interface. It’s cleaner, it’s lower latency, and for a development server, latency is everything.
What about Docker vs. LXC?
Docker does not have the “double kernel” overhead on native Linux. Like LXC, it uses the host’s native kernel directly via namespaces and cgroups to isolate processes.
Linux (Native)
Zero double-kernel tax. Docker containers are just restricted processes on the host kernel.
Windows & macOS
Yes, double-kernel overhead exists. Since these OSs lack the specific Linux kernel features required to run Linux containers, Docker Desktop spins up a lightweight Linux VM (via WSL2 or Hyper-V). Your containers run inside that VM’s kernel, which then talks to your host’s kernel—exactly like the Multipass problem.
Warning: If you run Docker inside an Ubuntu VM on Proxmox, you are re-introducing the “Double Kernel” tax.
The Engineering Verdict
If you have a latest-gen processor with 128GB of RAM, use whatever you want. Abstraction is cheap when you have resources to burn.
But if you have old hardware, you cannot afford the “Double Kernel” tax of Multipass. You need the efficiency of the Proxmox + LXC stack. Give each of your 2–3 users an LXC container. They get the “feel” of a full Ubuntu server, they can apt install whatever they want, but the underlying kernel overhead is effectively zero.
At the end of the day, it’s about staying close to the hardware. Minimize the layers, minimize the syscall overhead, and your “old” hardware will feel like a brand-new machine.