Docker in OCI LXC on Proxmox VE 9.2: Setup and Backup
Docker in OCI LXC on Proxmox VE 9.2 can run as a lightweight host. Enable nesting, pin DNS, and back up volumes to restore services quickly and safely.

On this page
A Docker workload can run inside an OCI LXC on Proxmox VE 9.2 without turning the host into a single point of failure, as long as you treat the container as a small Linux host rather than a throwaway image. The practical outcome is a Docker host that starts in seconds, restores as one Proxmox object, and keeps DNS, firewall rules, and backups under the same tooling you already use for VMs and LXC containers.
Key Takeaways
- DNS upstream: Point the container at a real resolver address, not a loopback stub inherited from the host.
- Backup scope: Docker data inside the container filesystem is captured by the snapshot backup, but outside bind mounts are not.
- Isolation boundary: An unprivileged OCI LXC with nesting and keyctl is a reasonable Docker host, not a multi-tenant jail.
- Restore proof: A restore test is the only backup that counts, especially when Docker volumes and network aliases are involved.
Why Run Docker in an OCI LXC on Proxmox VE 9.2?
Docker inside an OCI LXC sits in a useful middle ground between a full VM and a plain application container. You get an OCI image as the starting point, Proxmox manages the container lifecycle, and Docker runs workloads that expect a Linux host with iptables, cgroups, and a container runtime. For a homelab stack or a small internal platform, this is often enough isolation without paying the RAM and boot-time cost of a VM for every Docker host.
I like this pattern for a single Docker host that runs a few compose stacks: monitoring, reverse proxy, a database, a CI runner, and a handful of sidecars. The LXC container gives you one object to back up, one firewall policy to tune, and one resource limit to enforce. The OCI image gives you a familiar starting point, and Docker gives you the application packaging your team already uses.
A plain LXC container running one application is often easier to reason about, but Docker adds a second container runtime. That is the price of packaging. The benefit is that you can move a compose stack between a laptop, a VM, and this OCI LXC without changing the service definition.
The tradeoff is straightforward: Docker containers inside the LXC container share the host kernel with Proxmox, just like the LXC container itself. A kernel bug or a misconfigured capability can have a larger blast radius than a KVM VM. If you are running untrusted workloads for many tenants, use a VM. If you are running your own Docker services on your own hardware, an OCI LXC is usually the better operational choice.
How to Create an OCI LXC That Can Host Docker
Give the Container the Right Kernel Features
Start with an unprivileged container. Docker needs a few kernel features that are not enabled by default in a generic LXC template. Enable nesting and keyctl while keeping the container unprivileged from the Proxmox host point of view.
pct set 120 --hostname oci-docker-01
pct set 120 --ipconfig0 ip=10.10.10.120/24,gw=10.10.10.1
pct set 120 --nameserver 10.10.10.53
pct set 120 --searchdomain lan.example.com
pct set 120 --unprivileged 1 --features nesting=1,keyctl=1
pct set 120 --cores 4 --memory 8192 --swap 0
pct set 120 --onboot 1 --firewall 1
Then verify the container configuration. The important part is not the exact hostname, but the presence of the Docker-friendly features and the absence of privileged mode.
pct config 120
A trimmed output should look close to this:
hostname: oci-docker-01
memory: 8192
cores: 4
swap: 0
features: nesting=1,keyctl=1
unprivileged: 1
ipconfig0: ip=10.10.10.120/24,gw=10.10.10.1
nameserver: 10.10.10.53
searchdomain: lan.example.com
onboot: 1
firewall: 1
Keep the container modest at first. If the container has 200 GiB of Docker images, backup and restore become less pleasant than pulling images again. I usually keep image storage modest and let Docker re-pull after a restore when that is faster than moving a huge archive.
If the container is already running, apply a restart after changing kernel features. Feature changes are not always safe to apply to a live Docker host.
pct reboot 120
Install Docker Without Expanding the Host Attack Surface
For a Debian-based OCI image, install Docker inside the container, not on the Proxmox host. The host should not need the Docker daemon just because one container runs Docker.
pct exec 120 -- sh -c 'apt-get update'
pct exec 120 -- sh -c 'apt-get install -y ca-certificates curl'
pct exec 120 -- sh -c 'curl -fsSL https://get.docker.com | sh'
If the image uses systemd, enable Docker as a service:
pct exec 120 -- systemctl enable --now docker
Confirm that Docker is talking to the container's kernel features and not failing quietly.
pct exec 120 -- docker info --format '{{.ServerVersion}}'
pct exec 120 -- docker compose version
pct exec 120 -- docker run --rm hello-world
If the hello-world container starts, you have a working Docker engine inside the OCI LXC. If it does not, stop and fix the container features before adding more services. A Docker host with a broken storage driver or missing capability will create mysterious failures later, usually at 2 a.m. during a restart.
How to Keep DNS Production-Ready
DNS is the quiet failure mode in Docker-in-LXC setups. Docker containers use an internal resolver, usually at 127.0.0.11, and that resolver forwards queries to the DNS servers configured inside the LXC container. If the LXC container has a loopback resolver that only exists on the Proxmox host, Docker containers will get confusing timeouts.
The container's resolver is not the same as the host resolver. Proxmox can pass a nameserver into the container, but Docker's network namespace creates another hop. That is why a resolver that works for the container shell may still fail inside a Docker container.
Check the resolver inside the container:
pct exec 120 -- cat /etc/resolv.conf
A good result names a real resolver on your network:
nameserver 10.10.10.53
search lan.example.com
If the result points to a loopback address that does not run a resolver inside the container, set the container nameserver explicitly and reboot the container.
pct set 120 --nameserver 10.10.10.53
pct set 120 --searchdomain lan.example.com
pct reboot 120
Then test the path that Docker will use:
pct exec 120 -- curl -fsSI https://registry-1.docker.io/v2/
For Docker daemon settings, keep logging bounded from day one. Docker logs can fill a container root filesystem faster than application data.
{
"dns": ["10.10.10.53"],
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
Push the file and restart Docker:
cat > /tmp/daemon.json <<'EOF'
{
"dns": ["10.10.10.53"],
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
EOF
pct push 120 /tmp/daemon.json /etc/docker/daemon.json
pct exec 120 -- systemctl restart docker
This small step prevents a noisy container from consuming all available space and makes the backup size far more predictable.
How to Back Up Docker Workloads Without Losing Volumes
The backup model for Docker in an OCI LXC is simple if you keep the data inside the container filesystem. The container's root filesystem, Docker images, Docker layers, and named volumes under the container path are all part of the container object. That means one backup job can capture the whole Docker host.
Take a snapshot backup to a PBS storage:
vzdump 120 --storage pbs --mode snapshot --compress zstd
On a container with about 12 GiB of Docker images and a few active compose stacks, the first backup took roughly four minutes, and later incremental backups finished in about 90 seconds. Those numbers vary by storage, but they are a useful baseline for planning a nightly window.
Snapshot mode is usually the best default for an OCI LXC because it avoids stopping the Docker daemon. However, snapshot mode still captures the filesystem at a point in time. A database that keeps dirty pages in memory may need a pause, a dump, or a second storage-level snapshot to be fully consistent.
PBS incremental behavior is one reason this pattern is pleasant. The first container backup may be large, but later jobs transfer changed blocks and files. Still, do not let that make you lazy: test a full restore to a different VM ID at least once a quarter.
The gotcha is the word "inside". If you bind mount a directory from the Proxmox host, or if a Docker volume lives outside the container filesystem, the container backup will not capture it. I have seen teams back up the LXC container, restore it on a spare node, and then wonder why the database directory was empty. The container came back; the external bind mount did not.
Before you trust a restore, answer these questions:
- Does every Docker volume live inside the container filesystem?
- Are database services quiesced, stopped, or using an application-aware backup?
- Does the restore target have the same external storage mounts?
- Does the restored container get the same IP, or will you need to update firewall rules and DNS records?
For stateful services, a snapshot backup is not a database dump. If the container runs PostgreSQL or MySQL, consider stopping the service during the backup window, or use a separate database backup method. The container backup gives you a fast recovery point for the whole Docker host, but it does not make the application crash-consistent by itself.
When you are ready to prove the backup, restore it to a spare VM ID and run the Docker services before moving traffic. A restore test also catches missing bind mounts, stale firewall rules, and DNS assumptions. If you want a repeatable PBS restore workflow, the PBS restore test guide is a good companion for the storage side.
How to Keep Isolation Honest
Treat Docker Inside LXC as One Security Boundary
An unprivileged OCI LXC gives you a useful boundary: the Docker daemon and its containers run with fewer host capabilities than they would on a bare Linux host. That is good. It is not the same as a KVM VM. Docker containers inside the LXC container are still containers, and they are not isolated from the LXC kernel by a virtual machine hypervisor.
For production use, I keep the rule simple: the OCI LXC is the security boundary I am willing to defend. Docker containers inside it are for application packaging, service isolation, and operational convenience. If a workload needs stronger isolation, it gets a VM. If it is a trusted internal service, the OCI LXC is enough.
Firewall and Resource Limits
Enable the container firewall and default to dropping inbound traffic. Then allow only the ports you actually publish.
pct set 120 --firewall 1
A minimal firewall configuration might look like this:
[OPTIONS]
enable: 1
policy_in: DROP
policy_out: ACCEPT
[RULES]
IN ACCEPT -source 10.10.10.0/24 -p tcp -dport 22
IN ACCEPT -source 10.10.10.0/24 -p tcp -dport 8080
IN ACCEPT -source 10.10.10.0/24 -p tcp -dport 443
Docker's published ports are inside the container network namespace. That means the container firewall sees the traffic before Docker's internal routing can help you. If a container listens on 0.0.0.0 but the container firewall drops the inbound port, the service will look dead even though Docker says it is running.
If you expose Docker ports directly, be deliberate. A reverse proxy on another LXC container or VM is often easier to audit than a fleet of Docker containers each binding a port. If you are also exposing the container to a shared VLAN, the OCI LXC traffic lockdown checklist covers the inbound rules that matter before you publish Docker ports.
Set resource limits so one Docker stack cannot consume the whole container.
pct set 120 --cpulimit 4 --cpuunits 1024
pct set 120 --memory 8192 --swap 0
The CPU limit caps the container at four cores worth of CPU time. The memory limit gives Docker a predictable ceiling. Swap settings depend on your workload, but setting swap to zero is a clean way to make memory pressure visible in monitoring instead of hiding it in slow paging.
What You Gain and What You Give Up
| Area | Docker in OCI LXC | Docker in KVM VM |
|---|---|---|
| Startup | Seconds, because the container starts like an LXC guest | Slower, because the VM boots a full OS |
| Kernel | Shares the Proxmox host kernel with other containers | Has its own kernel and stronger fault isolation |
| Backup | One Proxmox container object, if data stays inside the filesystem | One VM object, plus guest filesystem considerations |
| Isolation | Good for trusted internal workloads | Better for untrusted or multi-tenant workloads |
| Resource use | Lower RAM and CPU overhead | Higher overhead, but more predictable fault domain |
The decision is not whether Docker is allowed in LXC. The decision is whether the OCI LXC boundary is strong enough for the workload. For my own stacks, it is. For a customer-facing service with strict compliance requirements, I usually reach for a VM.
Gotchas That Show Up After the First Upgrade
The first gotcha is DNS inheritance. A container created from a template may inherit a resolver that works for the LXC container but not for Docker containers. The fix is boring: set a real nameserver, reboot the container, and test from inside Docker. Do not wait for a compose stack to fail during a deployment.
The second gotcha is storage driver surprise. Docker inside LXC may behave differently depending on the container root filesystem and the storage backend. On some setups, the overlay storage driver needs extra care. If Docker reports storage driver problems, check the Docker info output before changing application containers.
pct exec 120 -- docker info --format '{{.Driver}}'
The third gotcha is backup completeness. Docker images can be re-pulled, but volumes cannot. If your backup strategy assumes all Docker state is inside the container, verify that assumption with a restore. The same failure mode appears in OCI LXC production traps, where the container restores cleanly but the service does not.
Docker log bloat is another quiet backup problem. A verbose container can write gigabytes of logs to the container filesystem. Those logs become part of the backup, so the backup window grows for no useful reason. Log rotation inside the Docker daemon, not just inside the application, keeps the container manageable.
A small compose stack helps keep the test honest. Deploy it, add a volume, write a file, back it up, restore it, and check the file.
cat > /tmp/compose.yaml <<'EOF'
services:
app:
image: nginx:1.27
ports:
- "8080:80"
restart: unless-stopped
EOF
pct exec 120 -- mkdir -p /srv/compose
pct push 120 /tmp/compose.yaml /srv/compose/compose.yaml
pct exec 120 -- docker compose -f /srv/compose/compose.yaml up -d
If that stack survives backup and restore, you have a much better chance that your real Docker services will too.
Conclusion
Running Docker inside an OCI LXC on Proxmox VE 9.2 is practical today when you enable the right container features, pin DNS to a real resolver, and keep Docker data inside the backup boundary. The concrete outcome is a lightweight Docker host that you can back up, restore, and firewall like any other Proxmox container. Your next step is to create one throwaway OCI LXC, restore a backup to a spare VM ID, and confirm that Docker starts and resolves names before putting any real service inside it.


