If you’ve built your homelab around Docker containers — running Home Assistant, Jellyfin, Vaultwarden, Nginx Proxy Manager, and a dozen other services — you’ve probably told yourself the containers are isolated. They’re not. Not really.
Containers are process groups with namespace and cgroup boundaries. They share your host kernel. Every container on your machine is one syscall away from the same kernel your host OS depends on. That’s not a security model — it’s convenience with a label on it.
This article is not a scare piece. It’s a practical look at what isolation actually means, where containers fall short, and what you can do about it in a real homelab without rebuilding everything from scratch.
What Containers Actually Give You
Docker isolates processes using three Linux primitives:
- Namespaces — separate views of the filesystem, network, PIDs, users, and more
- Cgroups — resource limits (CPU, memory, I/O)
- Seccomp/AppArmor/SELinux — optional syscall filtering and MAC policies
What they do not give you: a separate kernel. Every container on your host makes syscalls directly to the same kernel your host userspace uses. If an attacker inside a container finds a kernel vulnerability — a bad io_uring path, a netfilter bug, a bad runc escape — they’re talking to the same kernel that owns your disks, your network interfaces, and every other container.
Container escapes are not theoretical. CVE-2019-5736 (runc), CVE-2022-0185 (kernel), and the long tail of io_uring vulnerabilities have all been exploited in practice. In a corporate environment with dedicated security teams, this is managed. In a homelab running as root with bind-mounted /var/run/docker.sock everywhere, it is not.
The Actual Threat Model for a Homelab
You probably aren’t running containers for zero-trust multi-tenant workloads. But consider what’s actually on your homelab:
- Services exposed to the internet (Nginx, Vaultwarden, Immich)
- Services pulling third-party container images updated automatically
- Services running as root inside the container
/var/run/docker.sockmounted into management containers like Portainer or Watchtower
That last point is particularly sharp. Mounting the Docker socket into a container gives that container full root equivalent on your host. If Portainer or Watchtower is ever compromised, the host is gone.
The threat model isn’t “nation-state APT.” It’s a vulnerable Vaultwarden version, a malicious container image update, or a CVE in a service you forgot to patch.
What Real Isolation Looks Like
There are three meaningful options beyond raw Docker:
Firecracker MicroVMs
Firecracker is Amazon’s open-source VMM, built on KVM. It boots a minimal Linux kernel in under 125ms with a ~5 MB memory footprint per VM. Each microVM has its own kernel. A kernel exploit inside one VM cannot touch another VM or the host kernel directly — it has to go through the hypervisor.
Firecracker is what powers AWS Lambda and Fargate. You can run it in your homelab on any machine with KVM support.
Kata Containers
Kata Containers wraps each container (or pod) in a lightweight VM using either QEMU, Firecracker, or Cloud Hypervisor as the backend. It integrates with containerd and Kubernetes, so you can use it as a drop-in runtime. Each container gets its own kernel while still being managed by your normal container tooling.
For a Proxmox-based homelab, Kata running on a dedicated VM with nested virtualization is the most practical path.
gVisor
gVisor takes a different approach: it intercepts syscalls in userspace via a Go-based kernel called “Sentry.” Containers run against gVisor’s syscall surface, not the host kernel. It’s not as strong as a separate kernel, but it massively reduces the host kernel attack surface. It runs natively with Docker and containerd with minimal configuration changes.
The Middle Ground: Hardening What You Have
Not everyone wants to rebuild their homelab around microVMs today. That’s fine. Here’s the minimum viable hardening for a Docker-based setup.
Drop Unnecessary Capabilities
# docker-compose.yml
services:
vaultwarden:
image: vaultwarden/server:latest
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
read_only: true
tmpfs:
- /tmp
user: "1000:1000"
volumes:
- ./data:/data
ports:
- "8080:80"
no-new-privileges prevents privilege escalation via setuid binaries. cap_drop: ALL removes every Linux capability and you add back only what’s needed. read_only: true with a tmpfs for /tmp prevents an attacker from writing to the container filesystem.
Enable a Seccomp Profile
Docker’s default seccomp profile blocks ~44 syscalls. That’s better than nothing, but you can go further with a custom profile that only allows exactly what your service needs. The Docker docs provide a default profile you can use as a starting point.
docker run --security-opt seccomp=/path/to/your-profile.json your-image
For AppArmor, Docker auto-generates a docker-default profile. You can load custom profiles with:
apparmor_parser -r -W /etc/apparmor.d/your-custom-profile
docker run --security-opt apparmor=your-custom-profile your-image
Never Mount the Docker Socket
Replace Portainer with Portainer’s agent model or switch to a read-only socket proxy like Tecnativa’s docker-socket-proxy that restricts which API endpoints are exposed.
Running Firecracker on a Homelab Node
If you want to go further and start running actual microVM workloads, here’s the minimal path on a Debian/Ubuntu host with KVM:
# Verify KVM is available
ls /dev/kvm
# Download Firecracker
ARCH=$(uname -m)
release_url="https://github.com/firecracker-microvm/firecracker/releases"
latest=$(curl -s "${release_url}/latest" | grep -oP '(?<=tag/)[^"]+')
curl -Lo firecracker "${release_url}/download/${latest}/firecracker-${latest}-${ARCH}.tgz"
tar -xzf firecracker-*.tgz
sudo mv release-*/firecracker-* /usr/local/bin/firecracker
# Verify
firecracker --version
From here, you use the Firecracker API (a Unix socket) to configure a kernel image, rootfs, network interface, and boot the microVM. For managing multiple microVMs, Flintlock and Ignite provide higher-level orchestration.
For a Proxmox homelab, create a dedicated VM with CPU type set to host and nested virtualization enabled, then run Firecracker inside that VM.
What to Actually Do This Week
If you do nothing else:
- Audit every container running with
--privilegedor with/var/run/docker.sockmounted. Remove it. - Add
no-new-privileges: trueandcap_drop: ALLto every compose file. - Run containers as non-root users wherever possible.
- If you’re on Proxmox, move internet-facing services into proper KVM VMs rather than LXC containers.
If you want stronger isolation without abandoning your workflow, install gvisor and switch your high-risk containers to the runsc runtime. It’s a single config change in /etc/docker/daemon.json and your compose files stay the same.
MicroVMs are the right long-term answer for homelab services you care about protecting. Containers are fast and convenient — they’re just not sandboxes.
[discussion]
Comments are powered by Giscus — backed by GitHub Discussions. Sign in with GitHub to join the conversation.