Proxmox VE 9.1 added a feature that removes a long-standing workaround: it can pull a Docker image straight from a registry and run it as an LXC container. No Docker daemon, no dedicated VM, no “install Docker inside a container and hope the nesting holds”. The image runs as a native, unprivileged LXC guest on the host kernel. On the test box an nginx container from Docker Hub came up in under two seconds and idled at roughly 5 MiB of working-set memory.
This guide covers the exact path: pull an OCI image from the web UI or the CLI, create a container from it, read what Proxmox imported from the image (entrypoint, environment, stop signal), start it, and measure what it costs. Building a full system container from an OCI image is fully supported in 9.1; the single-service application-container path shown here is still marked tech preview, so its limits are documented at the end rather than glossed over.
Tested July 2026 on Proxmox VE 9.2.2 (the OCI-image feature was introduced in 9.1).
What “OCI image as LXC” actually means
An OCI image is the packaging format behind every Docker image. Proxmox VE 9.1 teaches the storage layer to fetch one with skopeo, unpack it, and register it as a container template. From there it behaves like any LXC guest managed by pct and the web UI.
Proxmox distinguishes two shapes. A system container is a full distribution image (Debian, Ubuntu, Alpine) with its own init. An application container is a single-service image such as nginx, Mosquitto, or Uptime Kuma: it runs the image’s entrypoint as the container’s main process, with no init and no login shell. The nginx image used here is an application container. That distinction is why the memory footprint is small and why the container has no interactive console in the usual sense. It also matters for support status: building a system container from an OCI image is a fully supported feature in 9.1, while the application-container path carries the tech-preview label.
The datacenter view below shows the end state: an lxc guest named nginx-oci sitting next to the storages on a single node, indistinguishable from any other container in the tree.

Prerequisites
- Proxmox VE 9.1 or later. Earlier releases have no OCI support. If you are on 8.x, upgrade first with the Proxmox VE 9 install and upgrade guide.
- A storage that accepts container templates. The default
local(directory) storage with content typevztmplworks without any extra configuration. - Outbound HTTPS from the node to the registry (Docker Hub, GHCR, Quay, or any OCI-compliant registry).
Step 1: Pull an OCI image
In the web UI, select the storage in the resource tree (here local), open the CT Templates tab, and click Pull from OCI Registry. It is a new button, next to the older Upload and Download from URL actions.

The dialog takes an image reference and a tag. Enter the repository (for example docker.io/library/nginx), click Query Tags to list what the registry offers, pick a tag such as stable, and download. The reference syntax is the same one Docker uses, so ghcr.io/owner/app or quay.io/org/app work identically.

The same operation from the shell uses the storage API. Replace pve with your node name:
pvesh create /nodes/pve/storage/local/oci-registry-pull --reference docker.io/library/nginx:stable
That call runs skopeo, which copies the image layers into the storage. The task log shows each blob and the final manifest write:
Getting image source signatures
Copying blob sha256:77c9d82cd1ff67ccbc8429b88d3313f774545002ae0a9b3684f4d8cec5ff0a73
Copying blob sha256:ac47ae235161dfad0a2a6dd6c2bb45c09c191ee0af3f062d68b764f3f32cde73
Copying config sha256:f91c43d45520959a835dfeb3d9e62ec52610b983c6c20ea409b2f212b2d38fb0
Writing manifest to image destination
TASK OK
The web UI exposes the same output in the Task viewer, which is the quickest place to confirm a pull succeeded:

The image lands as a normal container template. On the test box the nginx image was 60 MiB on disk:
pveam list local
The pulled image appears with the tar format and its size:
NAME SIZE
local:vztmpl/nginx_stable.tar 60.19MB
Step 2: Create the container
Create the container the same way you would from any template, pointing pct create at the pulled tarball. The example below allocates 512 MiB of memory, one core, a 3 GiB root disk, and a DHCP address on vmbr0, as an unprivileged container:
pct create 200 local:vztmpl/nginx_stable.tar \
--hostname nginx-oci \
--memory 512 --cores 1 \
--rootfs local-lvm:3 \
--net0 name=eth0,bridge=vmbr0,ip=dhcp \
--unprivileged 1
Proxmox recognizes the tarball as an OCI image and adjusts the networking model automatically:
Detected OCI archive
Auto-Enabling host-managed network for network device net0.
Host-managed networking is the key difference from Docker. A Docker container relies on port publishing (-p 8080:80); an OCI-as-LXC container gets its own address on the bridge, exactly like a normal LXC guest, and is reachable directly on its service port. The same wizard exists in the UI: Create CT, then pick the OCI image on the Template tab.
Step 3: Read what Proxmox imported from the image
This is where an OCI-derived container differs from a plain LXC. Proxmox translates the image metadata into the container config. Inspect it:
cat /etc/pve/lxc/200.conf
The entrypoint, the image’s environment variables, and the stop signal all come straight from the OCI metadata:
arch: amd64
cores: 1
entrypoint: /docker-entrypoint.sh nginx -g 'daemon off;'
hostname: nginx-oci
memory: 512
net0: name=eth0,bridge=vmbr0,host-managed=1,hwaddr=BC:24:11:80:4D:C4,ip=dhcp,type=veth
ostype: debian
rootfs: local-lvm:vm-200-disk-0,size=3G
unprivileged: 1
lxc.environment.runtime: NGINX_VERSION=1.30.4
lxc.environment.runtime: NJS_VERSION=1.0.0
lxc.signal.halt: SIGQUIT
The entrypoint line is what the container runs as PID 1. The lxc.environment.runtime entries are the image’s ENV values, and you can edit or add to them under Options > Environment in the UI (a restart applies them). The lxc.signal.halt: SIGQUIT line means a clean shutdown sends nginx the graceful-stop signal the image declared, not a blunt SIGTERM.
Step 4: Start it and measure the footprint
Start the container:
pct start 200
The summary tab tells the whole story. The container is running, unprivileged, holding its own DHCP address, and the working set is 4.91 MiB of the 512 MiB limit. The root disk uses 165 MiB of the 2.88 GiB allocated.

Because it is an application container, you interact with it through pct exec rather than a login console. Confirm the service and the base image:
pct exec 200 -- nginx -v
pct exec 200 -- cat /etc/os-release | head -1
Both return the values baked into the image:
nginx version: nginx/1.30.4
PRETTY_NAME="Debian GNU/Linux 13 (trixie)"
The real test is whether it serves. From another host on the bridge, request the container’s address (shown on the summary tab):
curl -I http://192.168.1.119/
nginx answers on its own IP, no port mapping involved:
HTTP/1.1 200 OK
Server: nginx/1.30.4
Content-Type: text/html
Content-Length: 896
For comparison, the same nginx running inside a Debian VM starts with 150 MiB to 200 MiB of guest kernel and systemd overhead before nginx allocates a byte. A traditional Debian or Ubuntu LXC container with nginx installed sits in the tens of MiB once its init and services are up. The single-service OCI container skips all of that.
Tech-preview limits worth knowing
The feature is genuinely useful today, but it is not a Docker replacement and Proxmox does not pretend it is. What held true on the test box:
- One image, one container. There is no Compose equivalent, so a multi-container application (app plus database plus cache) means creating and wiring each container yourself.
- No Docker API, so tools that expect one (Portainer,
docker compose, CI runners that shell out to Docker) do not attach. - No update by swapping the tag. Pulling a newer image does not upgrade a running container; you recreate it against the new template and reattach any data volume.
- An application container has no login shell, so the noVNC Console is not the way in. Use
pct execorpct enterfor a shell when the image ships one. - Persistent data belongs on a mount point (
--mp0) or a bind mount, the same as any LXC. The container rootfs is disposable when you recreate it.
OCI-as-LXC versus the alternatives
Where this fits against the two things Proxmox users did before 9.1:
| Dimension | OCI image as LXC | Docker inside a VM | System LXC template |
|---|---|---|---|
| Idle memory (nginx) | ~5 MiB working set | 150-200 MiB (guest) + Docker | tens of MiB |
| Boots the vendor image | Yes, unmodified | Yes | No, you install the service |
| Multi-container / Compose | No | Yes | N/A |
| Update by new image tag | Recreate container | docker pull + recreate | apt/dnf upgrade |
| Networking | Own IP on the bridge | Port mapping / NAT | Own IP on the bridge |
| Best for | Single-service self-hosted apps | Full Docker/Compose stacks | Long-lived OS containers |
For a single self-hosted service (a reverse proxy, an MQTT broker, a bookmark manager) that you want running on its own IP with a few MiB of overhead, pulling the vendor’s OCI image directly is now the shortest path on Proxmox. For a full multi-service stack that expects Compose or the Docker API, keep that on a dedicated VM alongside a tool like Docker Compose. Watch the tech-preview status: the mechanics shown here are stable, but the surrounding tooling (updates, multi-container orchestration) is where Proxmox is still building, so pin anything production-critical to a recreate-from-template workflow until it graduates. New to Proxmox containers generally? The post-install checklist covers the storage and network groundwork this feature builds on.