Good morning, Agent.
You’ve been running Docker inside LXC containers on Proxmox for over a year now. Ten containers. Ten little fortresses, each housing its own Docker Compose stack. Immich, Frigate, Jellyfin, the Arr empire. Clean separation. Independent backups. Feels right.
Then someone drops a Reddit thread in your lap: “Docker in LXC is an antipattern.”
Time to audit the operation.
The Setup Under Scrutiny
Here’s how the Minilab is architected. Every service gets its own LXC container on Proxmox, and inside each one, Docker runs the actual application:
Proxmox Host (Intel N97)
├── LXC 100: Uptime Kuma (Docker)
├── LXC 104: Immich (Docker)
├── LXC 105: Jellyfin (Docker)
├── LXC 106: Arr Stack (Docker)
├── LXC 107: Frigate (Docker)
├── LXC 109: Zigbee2MQTT (Docker)
├── LXC 110: Speedtest Tracker (Docker)
├── VM 101: Home Assistant OS
└── Tailscale LXC (no Docker)
Every Docker-capable LXC has nesting=1 enabled. Most of the interesting ones – Immich, Jellyfin, Arr, Frigate, Zigbee2MQTT – run as privileged containers because they need bind mounts to external storage or device passthrough.
The whole thing is managed by Ansible. Playbooks create the LXCs, install Docker, template out docker-compose.yml files, and bring stacks up. Repeatable. Consistent. Elegant, even.
So what’s the problem?
The Case Against
The critics aren’t wrong. Their core argument has teeth:
You’re nesting containers inside containers. LXC is a container runtime. Docker is a container runtime. Running Docker inside LXC means you’re paying for two layers of namespace isolation, two layers of cgroup management, two layers of filesystem abstraction. On a 16GB N97, that overhead isn’t free.
Privileged LXC defeats the purpose. The main security argument for LXC is unprivileged execution, user namespace mapping that prevents container root from being host root. But six of my containers run privileged because they need USB passthrough (Zigbee2MQTT), GPU access (Jellyfin), or bind mounts to external storage (Immich, Arr). At that point, the “isolation” is a gentleman’s agreement, not a security boundary.
Three-layer storage is asking for trouble. Consider how a photo reaches Immich:
USB Drive → Proxmox bind mount → LXC mount point → Docker volume → Immich
That’s four hops. When something breaks, you’re debugging through three layers of mount namespaces. I’ve been there. It’s not fun.
The alternative is simple. Run Docker directly on Proxmox, or in a single VM. One layer. No nesting. The Docker Compose ecosystem still works. You just lose per-service Proxmox snapshots.
The Case For
But here’s the thing. The critics are arguing from theory. I’m arguing from a year of running this in production.
Per-service backup granularity is real. When I update Frigate and it breaks, I restore that one LXC snapshot. Immich keeps serving photos. Jellyfin keeps streaming. The blast radius is one container. With a single Docker VM, a bad docker-compose pull on the wrong stack and a moment of inattention could cascade.
Resource limits are enforced by Proxmox, not Docker. Docker’s resource constraints are suggestions. Proxmox’s are real. When Frigate’s ML detection spikes CPU, it hits the wall I set in the LXC config. Immich’s ML model doesn’t starve. On a constrained N97, this matters.
The Docker ecosystem is non-negotiable. Immich ships 6 containers (server, ML, Redis, Postgres, microservices, proxy). Frigate ships with its own Coral TPU integration, MQTT client, and Go2RTC. The Arr stack is 5 containers. These projects are designed for Docker Compose. Installing them “natively” in LXC would mean maintaining custom packages, dependency conflicts, and manual update procedures. I’d spend more time being a package maintainer than running a homelab.
The overhead is measurable, and it’s small. Each LXC adds roughly 30-50MB of base RAM. That’s the cost of the extra namespace layer. On 16GB, running 10 LXCs costs me maybe 400MB. I can afford that for the operational benefits.
The Honest Verdict
Am I doing an antipattern? Technically, yes. Nesting containers is extra complexity, and privileged LXC undermines the isolation story.
Am I going to change it? No.
Here’s why. The alternatives all trade one problem for another:
| Approach | What you gain | What you lose |
|---|---|---|
| Docker on bare Proxmox | Simplicity, no nesting | All isolation, can’t snapshot services independently |
| Single Docker VM | Clean single layer | Per-service backups, granular resource control |
| Multiple VMs | Real isolation | 2-4GB RAM per VM, N97 can’t handle it |
| Native LXC (no Docker) | Lightest weight | The entire Docker ecosystem, your weekends |
The “right” answer depends on your hardware and your priorities. On a 64GB Xeon rack server, I’d probably run a single Docker VM and call it a day. But on a 16GB N97 where RAM is precious and operational simplicity matters, the Docker-in-LXC approach hits a sweet spot: lighter than VMs, more isolated than bare Docker, and compatible with the entire self-hosted ecosystem.
What I’d Do Differently
If I were starting over, two changes:
Fewer privileged containers. Some of my containers are privileged out of convenience, not necessity. I’d spend the time to make unprivileged work with the right
lxc.idmapandlxc.cgroup2.devices.allowentries. Every privileged container you eliminate is a real security improvement.Group related services. The Arr stack (Radarr, Sonarr, Prowlarr, qBittorrent, FlareSolverr) doesn’t need five separate LXCs. One LXC with one Docker Compose stack is fine – they share a failure domain anyway. This would reduce LXC count and free up resources.
The Takeaway
The Docker-in-LXC pattern isn’t clean. Purists will tell you it’s wrong. But purity doesn’t run Immich, back up your daughter’s photos, or keep Frigate watching the front door.
The real antipattern isn’t nesting containers. It’s spending so long optimizing your architecture that you never actually use it.
Run the workload. Monitor the resources. Fix what actually breaks.
And check the backups.
Mission status: holding. The architecture is unorthodox, the tradeoffs are understood, and the backups run every night. Sometimes that’s enough.
