Docker Compose on Proxmox VE 9.1 OCI LXC Setup Guide
Docker Compose Proxmox VE OCI LXC setups keep nested services isolated while PBS snapshots and firewall rules protect data, enabling fast, reliable recovery.

On this page
You can run Docker Compose stacks on a Proxmox VE 9.1 OCI LXC and keep backups, firewalling, and unprivileged isolation if you treat the OCI LXC as a normal LXC container for lifecycle operations and put the Docker daemon inside the container. The payoff is a small, restore-friendly service box: Proxmox Backup Server snapshots the whole container, Proxmox firewall rules protect the exposed ports, and Docker Compose still gives you multi-container application isolation.
Key Takeaways
- Treat OCI as LXC: use normal Proxmox container management, firewalling, and backup paths after the OCI image container is created.
- Back up the whole container: keep Docker images, containers, and volumes inside the container storage so Proxmox Backup Server can snapshot them.
- Set features once: enable nesting only on the container that hosts Docker, and restart after changing it.
- Firewall at Proxmox: let the container firewall protect the service ports, then expose only what Docker Compose should publish.
- Keep Docker data inside: avoid host bind mounts from the Proxmox host unless you back up those paths separately.
What Counts as an OCI LXC in Proxmox VE 9.1?
An OCI LXC in Proxmox VE 9.1 is a container created from an OCI image, but once it is running, it behaves like a normal LXC container for resource limits, firewalling, and backup operations. That distinction matters because the OCI image only provides the base filesystem; Docker Compose runs inside that container as a nested runtime.
I created my test container from the Proxmox VE 9.1 web UI using the Debian 12 OCI image and assigned it CTID 110. If you use a standard LXC template instead, the same container settings and backup behavior apply. The OCI path is mostly about where the root filesystem comes from, not about giving you a different backup or firewall model.
If you are new to this pattern, start with a standard LXC container and Docker Compose, then move to OCI images when you want a smaller base image. For the OCI image import path, see OCI image containers in Proxmox VE 9.1. For baseline Docker LXC isolation, see Docker LXCs on Proxmox.
How to Create and Configure the Container for Docker Compose
Create the container with the right shape
Do not run Docker on the Proxmox host just because it is convenient. A dedicated unprivileged container is easier to back up, firewall, and destroy than a host-level Docker install that becomes tangled with host services.
For the container itself, I used the following resource settings. They are modest but comfortable for a small Compose stack with a database.
pct set 110 --memory 4096 --cores 2 --cpulimit 2 --cpuunits 1024 --onboot 1 --firewall 1
Then enable nesting so Docker can create nested container processes inside the LXC.
pct set 110 --features nesting=1
pct restart 110
If the container root filesystem is on ZFS and Docker needs the fuse overlay driver, add that feature as well.
pct set 110 --features nesting=1,fuse=1
pct restart 110
Give the container a stable address and firewall rule
Docker inside the container creates its own bridge network, but the container itself still needs a predictable address on the Proxmox network. I gave the container a static address and enabled the container firewall.
pct set 110 --net0 name=eth0,bridge=vmbr0,ip=192.168.10.110/24,gw=192.168.10.1
pct set 110 --firewall 1
pct set 110 --onboot 1
pct restart 110
Enable the container firewall at the node or datacenter level first, then add rules for only the ports you want to expose from the container. Docker Compose can publish ports inside the container, but Proxmox firewall rules are what protect the container from the rest of your network.
If your nested Docker setup trips seccomp or AppArmor denials, see LXC seccomp profile on Proxmox VE. In my lab, the common fix was to keep the container unprivileged, enable nesting only where needed, and avoid granting more host access than necessary.
How to Install Docker Inside the OCI LXC
Open a root shell inside the container. On Debian 12, the official Docker repository install path works cleanly. I tested this with Docker Engine 27.5.1 and the Docker Compose plugin v2.
apt-get update
apt-get install -y ca-certificates curl gnupg
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian bookworm stable" > /etc/apt/sources.list.d/docker.list
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
If you are on ZFS and need the fuse overlay driver, install it inside the container.
apt-get install -y fuse-overlayfs
Write the Docker daemon JSON file inside the container with log limits and a storage driver that matches your container storage. For ext4 or LVM-thin storage, overlay2 is the normal choice.
{
"storage-driver": "overlay2",
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
If the container root filesystem is on ZFS and Docker fails with overlay2, switch the storage driver to fuse-overlayfs and restart Docker.
{
"storage-driver": "fuse-overlayfs",
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
Restart Docker after changing the daemon configuration.
systemctl restart docker
Verify the daemon and Compose plugin are working.
docker info --format '{{.ServerVersion}}'
docker compose version
How to Keep Networking and Security From Pulling Apart
Do not mount the host Docker socket into the OCI LXC. That shortcut gives the container root-equivalent access to the host Docker daemon, and it also puts your backup story in a bad place because the real Docker data is no longer fully inside the container.
Keep the Docker daemon inside the OCI LXC. Keep Docker images, containers, and volumes inside the container. Keep network access controlled at the Proxmox container firewall. That gives you a clean boundary:
- Proxmox controls the container resources and network path.
- Docker Compose controls the services inside the container.
- Proxmox Backup Server controls the container backup.
- The container firewall controls what other hosts can reach.
Use Docker Compose bridge networks for service isolation, and publish only the ports you need. If you publish a service to all interfaces, make sure the container firewall restricts it. If you need host networking inside the container, do it on purpose and document why.
For a small homelab stack, I usually expose only HTTP and HTTPS to the LAN, then let a reverse proxy handle authentication and routing. Database ports stay on the Docker bridge unless another service genuinely needs them.
How to Make Backups Work the Way You Expect
The biggest win of running Docker Compose inside an OCI LXC is that Proxmox Backup Server can back up the whole container, including the Docker data directory, as long as that data lives inside the container storage.
If your Proxmox Backup Server storage is named pbs, this command takes a snapshot backup of CTID 110.
vzdump 110 --storage pbs --mode snapshot --compress zstd
A 12 GB compose stack with a Postgres data volume took about 90 seconds to snapshot and 4 minutes to verify restore on PBS in my lab. That is fast enough to run nightly and still leave room for a second copy.
My first restore failed because the compose file used a bind mount to a host path that was outside the container storage. PBS backed up the container, not the host mount. The fix was simple: move the data into a named Docker volume inside the container. If you must use a host path, back it up separately or put it on a dataset that is included in the backup job.
If you replicate those Proxmox Backup Server backups, PBS 4.2 parallel sync jobs are the next thing to tune.
Compose Layout That Survives Restores
The compose file should use named volumes, healthchecks, and restart policies that match your backup and restore workflow. Keep secrets out of the file if you store it in version control, and do not point volumes at random host paths.
services:
app:
image: nginx:1.27-alpine
restart: unless-stopped
environment:
TZ: UTC
volumes:
- app-data:/usr/share/nginx/html
healthcheck:
test: ["CMD", "wget", "--spider", "http://127.0.0.1:80/"]
interval: 30s
timeout: 5s
retries: 3
depends_on:
db:
condition: service_healthy
networks:
- compose-net
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_PASSWORD: example
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 30s
timeout: 5s
retries: 5
networks:
- compose-net
volumes:
app-data:
db-data:
networks:
compose-net:
driver: bridge
Create the project directory inside the container and bring the stack up.
mkdir -p /opt/stacks/homeapp
cd /opt/stacks/homeapp
docker compose pull
docker compose up -d
Use healthchecks so dependent services do not start before the database is ready. Use named volumes so the data lives under the Docker data directory inside the container. Use a simple restart policy so the stack comes back after a container restart, but do not rely on that alone for disaster recovery.
Comparison: Where This Pattern Fits
| Option | Isolation | Backup scope | Best fit |
|---|---|---|---|
| Unprivileged OCI LXC | Shared kernel with user namespace and container limits | Proxmox Backup Server backs up the container root filesystem and named Docker volumes | Homelab service boxes that need easy restore and firewall control |
| Privileged LXC | Shared kernel with more host access | Same Proxmox Backup Server scope | Hardware access or nested runtime quirks that cannot be solved otherwise |
| KVM VM | Strong kernel isolation | Whole virtual machine disk | Multi-tenant, kernel boundary, or strict security requirements |
| Host Docker | Lowest isolation | Host backup only | Temporary single-purpose hosts where backup and firewall boundaries are less critical |
The honest tradeoff is that an OCI LXC still shares the host kernel. That is fine for most homelab services, but if you need a hard kernel boundary, a KVM VM is the safer answer. The pattern works best when the container is treated as a disposable service box with clean backups, clear network rules, and no accidental host socket mounts.
Conclusion
Docker Compose on a Proxmox VE 9.1 OCI LXC works well when the container is unprivileged, nesting is enabled only where needed, Docker data stays inside the container storage, and Proxmox firewall rules protect the exposed services. The next step is to create one small Compose stack, back it up to Proxmox Backup Server, and test a restore before you put anything important on it.


