What it is
The standard pattern I use for any Docker workload in the homelab: a dedicated LXC, with Docker installed inside, running one logical stack of services. I have five of these across the two Proxmox nodes — edge infrastructure, password manager, media stack, dev tools, and a sandbox for experiments.
Why I run it this way
The default question for a self-hoster is "one big Docker host or many small ones?" The answer for me is many small ones. Specifically:
- Blast radius. If Plex's nightly maintenance burst fills the rootfs (which has happened), only the media stack is affected. The password manager, the reverse proxy, and the dev environment are untouched.
- Backup granularity. PBS snapshots whole LXCs. A per-stack LXC means each stack gets its own snapshot, and a restore brings back exactly that stack — no cross-contamination.
- Resource isolation. The CPU/memory ceiling on each LXC bounds the worst-case impact of a runaway container. A leaky n8n workflow can't starve the reverse proxy.
- Deploy independence. I can rebuild the media-stack LXC from scratch without touching anything else.
The cost is a bit of duplication — five Docker installs, five Watchtower instances, five Portainer agents. That's been a fair trade.
How I use it
Each Docker LXC follows the same playbook: unprivileged container with nesting=1,keyctl=1 features, static IP (never DHCP — routers lose reservations on firmware updates), Docker installed from the official Docker apt repo (not docker.io), Container Protection enabled in Proxmox to prevent accidental destroy. Services live as docker-compose.yml files under /opt/stacks/<service>/, version-controlled where it matters.
The five current LXCs:
- docker-edge — Nginx Proxy Manager, AdGuard primary, Uptime Kuma, Homepage, Portainer.
- vaultwarden-host — Vaultwarden, isolated for blast-radius reasons.
- docker-media — the entire media stack (Plex, Tautulli, Overseerr, Riven, Seanime, AniBridge) plus the rclone-zurg FUSE mount.
- docker-tools — Stirling PDF, the Prometheus/Grafana observability stack, and a few utilities.
- docker-sandbox — code-server and experimental projects. The one I'm willing to break.
Every Docker LXC also runs a Portainer agent for unified container management, and its own Watchtower instance configured monitor-only (notifies me of available updates but never applies them).
Setup notes
- Day-zero hardening:
apt update && apt upgrade, install Docker from the official repo,docker run --rm hello-worldto smoke-test. - If the LXC has a separate data mount: set Docker's
data-rootto point at it before starting any stacks. Forgetting this is how rootfs fills up at 3 AM. - FUSE workloads (rclone, sshfs): unprivileged LXCs need explicit
/dev/fusepassthrough and cgroup permissions in the LXC config. - Backups: every Docker LXC is in the PBS schedule. Multi-disk LXCs use stop-mode backups (one of them, with both
rootfsand a data mount, has a known cgroup-v2 freezer race in snapshot mode that hangs the container). - Update cadence: manual, alongside other infra updates. Watchtower notifies via a Discord webhook routed through an n8n workflow that adds release-notes context.
Runbook
- Healthy looks like: every LXC is running in the Proxmox UI, every Portainer agent reports "Up," every container in every stack is
Up X hours. - Pin every image. The lesson is reinforced regularly: an upstream container ships a regression,
:latestresolves to it, and you wake up to a broken service. Pin the tag, treat upgrades as planned events, never auto-update production stacks. - Rootfs filling up unexpectedly: usually a stack writing scratch data to
/var/lib/dockerbecause nobody setdata-rootto the LXC's larger data mount. Stop the stack, move/var/lib/dockerto the data mount, updatedaemon.json, restart Docker. - A new image works on one LXC but not another: kernel version differences. The two Proxmox nodes are on the same PVE version but a container that expects a specific cgroup driver or capability can still surprise you.
- Where logs live:
docker compose logs -f <service>inside the LXC, Portainer's per-container log viewer for ad-hoc reads.