Docker on Proxmox: VM vs LXC and the Setup Most Operators Land On

13 min read

Docker on Proxmox almost always means one of four setups: straight on the Proxmox host, inside a VM, inside an LXC container, or through Proxmox’s native OCI application containers. Only one of those four is the default most operators land on after trying the others, and it isn’t the one that saves the most RAM.

This guide assumes you’ve already made the broader LXC vs VM decision for other workloads and just want the Docker-specific answer: what to build, why, and what breaks if you build it differently.

Quick answer

Run Docker in a dedicated Linux VM. Give it VirtIO SCSI Single with IO Thread, no cache, discard enabled where the storage backend supports it, and qemu-guest-agent installed. Install Docker Engine from the official APT repository, keep application data on an explicit filesystem you can back up, and pair vzdump snapshots with application-level dumps for anything stateful. Docker-in-LXC and native OCI containers both have real uses, but neither is the production default for Docker on Proxmox.

Docker on Proxmox: The Four Deployment Options

  • Directly on the Proxmox host – technically possible, operationally wrong. Covered below.
  • Docker in a VM – the default recommendation for this guide. Stronger isolation, its own kernel, predictable Docker behavior, live migration support.
  • Docker inside an LXC container – lower overhead, but shares the host kernel and inherits its cgroup/AppArmor behavior.
  • Native Proxmox OCI application containers – a Proxmox VE 9.1+ feature that converts an OCI image into a managed LXC-backed container. Still a technology preview as of this writing.

Do Not Install Docker Directly on the Proxmox Host

The most common wrong answer to Docker on Proxmox is the simplest one: installing it directly on the hypervisor. Proxmox VE is an infrastructure host, not a general-purpose application server. Installing Docker directly on it mixes Docker’s own networking, iptables rules, storage paths, and package dependencies with the virtualization control plane that manages every other VM and container on the node.

It may run without any visible problem for months. The Proxmox VE FAQ doesn’t recommend running additional services on the host for this exact reason – it removes the management boundary Proxmox is designed to provide, and it widens the blast radius of a single misbehaving container to include the thing managing all your other VMs.

A Proxmox host has one job: run guests reliably. Every package installed directly on it – Docker included – is one more thing that can compete with that job for CPU, memory, iptables rules, or a kernel panic.

Use a VM, an LXC container with the known limitations in mind, or native OCI application containers instead. All three keep application deployment and lifecycle management outside the Proxmox host OS, although LXC-based options still share the host kernel. That separation lets you reboot, restore, or replace the workload without installing Docker directly on the hypervisor.

VM vs LXC vs Native OCI: Decision Table

The comparison most Docker on Proxmox searches are actually looking for:

DeploymentBest forMain advantageMain limitation
Docker directly on hostAlmost neverNo guest overheadMixes apps with the hypervisor control plane
Docker in a VMProduction stacks, Compose, multi-service appsIsolation, predictable behavior, live migrationExtra guest OS overhead
Docker in LXCLabs, low-stakes workloads, shared devicesLower overhead, simpler device mappingHost-kernel coupling, update fragility
Native OCI application containerSimple single-service workloadsProxmox-native managementTechnology preview, no Compose orchestration

Why a Docker VM Is the Default Recommendation

Most of the operational risk in Docker on Proxmox deployments traces back to skipping this default without a specific reason. Docker in a VM gets the guest’s own kernel, its own cgroup and AppArmor state, and its own update cycle. None of that is shared with the Proxmox host, so a Docker Engine upgrade, a storage-driver change, or a botched docker system prune stays contained to the guest.

Docker inside LXC saves RAM and disk, and it’s a reasonable choice for a homelab lab environment or for workloads that specifically need simple shared-device access. What it doesn’t give you is insulation from the host: a Proxmox kernel or cgroup update can change behavior underneath every nested Docker container at once, which is exactly the coupling a VM avoids.

Why operators get this wrong: they assume the LXC overhead savings apply cleanly to Docker the same way they do to a single lightweight service. Docker itself already adds a layer of namespacing and cgroup management – stacking that inside LXC’s own namespacing is where the fragility comes from, not from Docker being heavy.

These are Proxmox-specific choices, not generic Docker advice – the same 4-6 settings that separate a Docker VM built correctly from one that behaves unpredictably under load or resizes badly on top of your chosen storage backend over time.

SettingRecommended defaultNotes
Guest OSDebian 13 minimal or cloud imageUbuntu Server works equally well
CPU typehost on a single nodeUse a compatible model instead if you need live migration across different hardware
vCPU2-4 to startSize by expected container workload
RAMFixed allocation sized for peakSee ballooning note below
SCSI controllerVirtIO SCSI SingleRecommended Proxmox path for Linux VMs
IO ThreadEnabled per data diskImproves parallel disk handling under concurrent container I/O
CacheNo cacheSafe general Proxmox default, not a Docker-specific tuning
DiscardEnabled if the storage backend supports itPair with guest-side TRIM, see below
NetworkVirtIO, static IP or DHCP reservationKeeps reverse-proxy targets and port mappings stable
Guest agentInstalled and enabled in VM optionsNeeded for backup consistency, shutdown, IP reporting

Two settings deserve more nuance than a table row allows.

Discard is conditional, not automatic. Enabling it only helps if the entire chain supports thin reclamation: guest filesystem, virtual disk, and the underlying Proxmox storage backend. Docker’s constant layer churn means an unreclaimed disk grows quietly if any link in that chain is missing. Verify it rather than assume it:

systemctl status fstrim.timer fstrim -av

Ballooning is an optimization, not a capacity plan. A Docker host with an aggressively low balloon minimum can run short on memory during boot or during a burst of container startups, before the balloon driver has a chance to expand. Size the VM for expected peak load and treat ballooning as something layered on top, not the thing you rely on to make a too-small allocation work.

Two more Proxmox-specific details worth setting explicitly rather than leaving on install defaults: a VirtIO network adapter attached to the correct vmbr bridge (with a VLAN tag if your network segmentation uses one), and Docker’s own log driver, since container log growth is a more common disk problem in practice than the storage-layer issues that get more attention:

{ "log-driver": "local", "log-opts": { "max-size": "10m", "max-file": "3" } }

Storage Layout: Where Docker and Application Data Actually Live

Storage layout is where a Docker on Proxmox build quietly succeeds or fails months later, not at setup time. Inside a full Linux VM, Docker uses the guest’s own kernel and normal storage stack, without the nesting and host-kernel constraints that affect Docker-in-LXC. On fresh Docker Engine 29 installations, Docker uses the containerd image store with the overlayfs snapshotter by default. Hosts upgraded from older Docker releases may still be running the classic overlay2 storage driver unless they’ve been migrated – check which one you’re actually running before assuming a specific behavior applies.

ext4 is the simple default guest filesystem. XFS works too, but the classic overlay2 driver requires d_type=true on XFS, which is the default on modern XFS but worth confirming on an older image. Either way, don’t hand-edit anything under /var/lib/docker directly – that’s Docker’s own managed state, not a general-purpose directory.

For persistent and write-heavy application data, use Docker volumes or bind mounts on a normal guest filesystem rather than relying on the container’s writable layer, which isn’t designed to hold data long-term.

Bind mounts and named volumes get framed as a hard choice more often than they need to be. Use bind mounts when you want application data in an explicit, operator-visible filesystem tree that ordinary file-level backup tools can reach directly. Use named volumes when the application expects Docker-managed storage and direct host-path access isn’t required – they’re a standard Docker primitive, inspectable and backable up like anything else Docker manages. Whichever model you pick, document it, include it in your backup plan, and test the restore before you need it for real.

Failure scenario

A pattern that surfaces repeatedly in homelab and small-team setups: compose files and configuration live in a git repository for good reason, but a production .env file, a database password, or a registry credential ends up committed alongside them. Passwords, API tokens, private keys, and production .env files do not belong in git, sanitized or not – a .env.example with placeholder values is the safe version to commit.

Installing Docker Correctly on Debian

The install step is short, but it’s where a Docker on Proxmox build either starts clean or starts with two problems already baked in. Install Docker Engine from Docker’s official APT repository, not the convenience script. Docker’s own documentation reserves the convenience script for testing and development environments – it isn’t the path for a VM you intend to run as a production Docker host. Once the base image is built and tested this way, converting it to a template makes spinning up the next Docker host a clone instead of a repeat install.

After installation, two defaults deserve attention rather than a quiet accept. Adding your user to the docker group is common practice for avoiding sudo on every command, but that group membership is root-equivalent control over the host – anyone in it can mount the host filesystem through a container. And Docker manages its own iptables rules for published ports, which means a port can be reachable from outside the VM even when UFW or firewalld looks like it should be blocking it. Review published ports independently, using the DOCKER-USER chain or an equivalent policy point, and verify exposure from another host rather than trusting the guest firewall’s own view of things. This is also why the Proxmox firewall is worth layering in at the vmbr/VM level for Docker on Proxmox – it filters traffic before it ever reaches the guest’s own iptables rules, a separate control point from anything Docker manages internally.

Networking and Firewall Design

Networking for Docker on Proxmox is mostly ordinary Docker networking, with a handful of Proxmox-adjacent gotchas worth knowing up front. The default Docker bridge with published ports and a reverse proxy container in front (Traefik, Caddy, or Nginx Proxy Manager) covers most deployments without any special configuration. For a typical reverse-proxy-based web stack, only ports 80 and 443 usually need to be exposed to the LAN; everything else stays behind the proxy.

Docker’s default bridge subnet (172.17.0.0/16) can collide with LANs or VPNs using the wider 172.16.0.0/12 range. If containers can’t reach specific internal hosts, that’s worth checking before anything more exotic – bip or default-address-pools in daemon.json resolves it.

Macvlan gives containers their own LAN-routable IP, which some self-hosted setups want for services that expect a real network presence. The well-documented limitation: a macvlan parent interface generally can’t talk to its own child interfaces, so the Docker host itself may be unable to reach a container on its own macvlan network. The usual fix is a macvlan shim interface on the host with a route to the container subnet – and because that shim isn’t part of Docker’s own config, it needs to be persisted through a systemd unit or network config file, or it silently disappears on the next reboot.

If containers lose DNS resolution while the VM itself resolves names fine, don’t assume any single cause. Inspect Docker’s generated resolver configuration, upstream DNS settings in daemon.json, firewall rules, and any local resolver such as systemd-resolved running on the guest. The presence of a stub resolver address alone isn’t proof of the failure – it’s one thing to rule in or out during diagnosis, not a default explanation.

Backups That Actually Restore

Backup design is the part of Docker on Proxmox operators most often get partially right – the VM backs up cleanly, but nobody verified the database inside it restores. vzdump snapshot-mode backups with qemu-guest-agent installed are strongly recommended – the guest agent’s filesystem freeze/thaw makes the underlying filesystem consistent during the snapshot, and in current Proxmox VE the guest-agent integration needs to be enabled in the VM’s own options, not just installed inside the guest.

Filesystem consistency is not the same thing as application consistency. A snapshot backup can capture a database mid-transaction in a way that restores cleanly at the filesystem level but leaves the database in an inconsistent state. For PostgreSQL, MySQL, or MariaDB running in containers, layer an application-level backup on top – pg_dump, mysqldump, or an equivalent native tool – rather than relying on the VM snapshot alone. A PBS-based backup strategy handles the VM side of this well; the database dump is a separate, deliberate addition.

Docker VM restore checklist
  1. Confirm the VM boots and qemu-guest-agent reconnects.
  2. Confirm Docker starts automatically and compose stacks come up in the expected order.
  3. Check bind-mount ownership and permissions – a restore onto different underlying storage can shift them.
  4. Verify database recovery completed cleanly, not just that the container is running.
  5. Confirm the reverse proxy resolves routes to the restored containers.
  6. Check that custom Docker networks were recreated, not just the default bridge.
  7. If using macvlan or ipvlan, confirm the host-side shim configuration came back too.
  8. Confirm any passed-through GPU devices are visible where the container expects them.

See the PBS restore guide for the mechanics of the VM-level restore itself – the checklist above is what to verify once that restore completes.

Updates: Three Separate Lifecycles

A Docker VM has three update cycles that move independently: the Proxmox host, the guest OS, and the container images themselves. Treating them as one update event is how a routine host patch turns into an unplanned debugging session.

For container image updates, the tool landscape shifted recently. The original containrrr/watchtower project was archived in December 2025 and shouldn’t be the default recommendation for a new deployment – its outdated Docker API client also fails against Docker Engine 29 unless a workaround or maintained fork is used. For most operators, a safer update workflow is notification-only monitoring (Diun is the common choice) followed by a reviewed docker compose pull and docker compose up -d, rather than fully unattended updates on anything stateful.

The practical advantage of the VM path here: a Proxmox host kernel update doesn’t touch the Docker VM’s own kernel, cgroup state, or AppArmor policy. That decoupling is exactly what Docker-in-LXC gives up, and it’s the recurring source of the “worked fine until the last update” reports that show up around Docker-in-LXC specifically.

GPU and Hardware Access

Hardware access is the one place where the clean VM-default story for Docker on Proxmox gets genuinely complicated. For Intel iGPU transcoding workloads (Jellyfin, Plex, Frigate, Immich), an LXC container mapping /dev/dri directly is genuinely the simpler path, and the same iGPU can stay shared with the host and other containers at the same time. A VM needing GPU access instead uses PCI passthrough, and with conventional full-device passthrough, the host and other guests normally lose access to that GPU for as long as the VM is running – specialized approaches like SR-IOV or vGPU can change that tradeoff, but they’re a different setup with their own hardware and driver requirements.

This is a real fork for a Docker-on-Proxmox setup built around the VM default: if the workload needs iGPU sharing more than it needs Compose orchestration and live migration, that specific service may belong in an LXC even while the rest of your stack stays in the VM. Full VFIO passthrough details are covered in the GPU passthrough guide – the tradeoff here is which side of it fits the workload, not how to configure VFIO itself.

When Docker in LXC Still Makes Sense

Not every Docker on Proxmox workload needs the VM’s isolation. A single low-stakes service, a disposable test environment, or a workload that specifically benefits from shared device access is a reasonable candidate for Docker inside an unprivileged LXC container with nesting and keyctl enabled.

What changes the calculus: multi-container Compose stacks, anything stateful you’d hate to rebuild, and anything you want insulated from host kernel updates all point back toward the VM. The underlying architecture tradeoff, including RAM overhead between the two models, is covered in more depth in the LXC vs VM guide linked above. Docker-in-LXC on Proxmox also inherits the container’s own UID/GID mapping constraints for any bind-mounted data – the LXC bind mounts guide covers that mapping in detail if this Docker on Proxmox workload needs host-path storage rather than Docker-managed volumes.

Where Native OCI Application Containers Fit

Native OCI support is the newest option for Docker on Proxmox, and it’s easy to overrate based on the feature name alone. Proxmox VE’s native OCI support pulls a Docker-format image and runs it as a Proxmox-managed application container, still labeled a technology preview as of this writing. It fits best for simple single-service workloads whose state is either disposable or stored on explicit, Proxmox-managed mount points – a static web service, a small exporter, or a disposable test service.

Services like Vaultwarden, Uptime Kuma, or Pi-hole can run this way, but none of them are actually stateless – they can run as single application containers only if their persistent data is externalized onto its own mount point and included in a separate backup and update plan. Native OCI containers currently have no Compose-style multi-container orchestration and no in-place image update path, which rules them out for anything resembling the multi-service stacks this guide is otherwise built around.

Common Failure Modes

Most Docker on Proxmox support requests that aren’t covered above trace back to one of a handful of symptoms. These assume the VM itself boots fine – if the VM won’t start at all, that’s a different diagnostic path entirely.

Docker won’t start, or containers fail after a resize or reboot
  1. Check for a resized VM disk where the guest filesystem was never extended – a common trap with cloud images, where Proxmox shows a larger disk but the guest LVM/partition is still the original size.
  2. Check docker info for the active storage driver and confirm the expected one is actually running.
  3. Check journalctl -u docker for the specific daemon error rather than guessing from symptoms alone – the Proxmox logs guide covers where else to look on the host side.
  4. If the error mentions a missing overlay2 merged directory, treat it as stale storage-driver state, usually following an unclean shutdown or a disk move.

Practical takeaway: most Docker-in-VM problems trace back to one of three things – a guest filesystem that wasn’t resized after a disk grow, a subnet collision with the LAN, or an update applied to three different lifecycles at once instead of one at a time.

Final Recommendation

The setup most operators land on for Docker on Proxmox, after trying the alternatives, is a dedicated Debian VM, official Docker Engine install, VirtIO SCSI Single with IO Thread, no cache, discard where supported, and application data on a filesystem that’s part of a tested backup plan. It costs more RAM than Docker-in-LXC and more management overhead than the native OCI preview, and it pays that cost back the first time a host update lands and the Docker VM doesn’t even notice.

Docker-in-LXC still earns its place for low-stakes workloads and shared-device use cases, and native OCI containers are worth watching as the feature matures past preview status. For anything running a real Compose stack in 2026, the VM remains the default for Docker on Proxmox.

FAQ

Should Docker run in a VM or LXC on Proxmox?

For Docker on Proxmox, a VM is the right default for anything production-facing or running Compose stacks. LXC is a reasonable choice for low-stakes, single-container, or shared-device workloads where the host-kernel coupling is an acceptable tradeoff.

Can I install Docker directly on the Proxmox host?

Technically yes, operationally no. It mixes Docker’s networking and storage with the hypervisor control plane and widens the blast radius of any Docker failure to include the thing managing every other guest on the node.

How much RAM does a Proxmox Docker VM need?

2-4 GB covers a lean baseline before adding container workload requirements. Size the fixed allocation for expected peak load rather than relying on ballooning to make a too-small allocation work.

Should /var/lib/docker use a separate disk?

A separate disk or at least a separate filesystem is worth it once you’re running more than a couple of services – it keeps image and layer churn from fragmenting your application-data disk, and makes disk-level troubleshooting cleaner.

Does Proxmox back up Docker volumes?

vzdump backs up whatever is on the VM’s virtual disks, including named volumes and bind-mounted data that live there. It backs up the filesystem, not the application state inside a running database – that still needs a separate dump.

Is Proxmox native OCI a Docker Compose replacement?

No. It runs single containers from OCI images with no multi-container orchestration and no in-place update path as of this writing. It’s a convenience feature for standalone services, not a Compose replacement.

Can a Docker VM use an Intel iGPU?

Yes, via GPU passthrough, but conventional full-device passthrough typically hands the whole device to that VM. If sharing the iGPU across the host and multiple containers matters more than VM isolation for that one workload, an LXC container is usually the simpler path for it.

Should I use Watchtower in 2026?

Not the original project – it was archived in December 2025 and its outdated API client fails against current Docker Engine releases without a workaround. Diun for update notifications, followed by a reviewed manual update, is the safer current default.