If you've spent any time in the Proxmox VE 9.1 release notes, you've probably seen the phrase "OCI image support" and wondered what it actually means for you. Short version: as of Proxmox VE 9.1, you can pull a container image straight from Docker Hub or another registry and turn it into a running LXC container, without installing Docker anywhere. No Dockerfile, no docker run, no separate container runtime sitting on top of your host.
This is different from the more familiar approach of installing Docker inside a regular Proxmox LXC container and running docker compose up the way you might on a VM. That method still works fine, but it means you're running a full container engine inside a container — two layers of container tooling stacked on top of each other. The OCI image feature skips that entirely. Proxmox reads the image, converts it, and boots it as a native LXC container.
It's still labeled a technology preview, so don't rebuild your whole homelab around it tomorrow. But it's stable enough to experiment with, and it's a genuinely useful trick to have for small, single-purpose services.
What You'll Learn
By the end of this tutorial you'll know how to:
- Pull an OCI (Docker-format) image into Proxmox VE storage, both from the web GUI and from the command line.
- Create a working LXC container from that image using the Create CT wizard or
pct create. - Tell the difference between an "application container" built from an OCI image and a regular "system container" built from a normal template.
- Recognize and fix the handful of errors people actually run into with this feature.
We'll use nginx as the example image throughout, mostly because it's small, boring, and easy to verify with a single curl command. Swap it for whatever image you actually want once you've got the process down.
What Is an OCI Image, and What's an Application Container?
OCI stands for Open Container Initiative — it's the industry-standard format for container images, the same format Docker Hub, GitHub Container Registry, and Quay all use. When someone says "pull a Docker image," what they're really pulling is an OCI image. Docker just popularized the format; it doesn't own it anymore. An OCI image is basically a filesystem snapshot plus some metadata: what command to run when the container starts, what environment variables to set, which user to run as, and so on. It's meant to be portable — any runtime that understands the OCI spec can run it, not just Docker.
That's the part Proxmox VE 9.1 now understands. When you pull an OCI image into Proxmox, it downloads the image layers and repacks them as an LXC template — a .tar file that behaves like any other container template Proxmox has always supported. Under the hood, Proxmox uses skopeo to fetch and convert the image.
The container you end up with is what Proxmox calls an application container, as opposed to the system containers you're used to creating from a Debian or Ubuntu template. A system container boots something close to a full Linux install: systemd, a login shell, an SSH daemon if you set one up, multiple services running side by side. An application container runs exactly one process — whatever the image's entrypoint says to run — and nothing else. There's no init system inside it. When that one process exits, the container stops.
That distinction matters more than it sounds like it should, so keep it in the back of your mind as you go through the rest of this guide.
Why Would You Use It?
Honestly, for a lot of homelab use cases the existing options — Proxmox Community Scripts, or a Debian LXC container with the app installed by hand — are still the easier path, and I wouldn't rip out something that already works to switch to this. But OCI images earn their place in a few specific situations. If a project only publishes a Docker image and doesn't offer a native Debian or Alpine package, you'd normally spin up a full LXC container just to run one binary inside Docker. That's a lot of overhead for one process. Pulling the OCI image directly cuts out the middle layer.
It's also handy for quickly testing an image you found on Docker Hub without committing to a full Docker install on your host or in a VM. You get to see whether the thing actually does what the README claims in under a minute, then throw it away.
And because the resulting container is a normal LXC guest as far as Proxmox's backup and migration tooling is concerned, it shows up in vzdump backups and the resource tree the same as everything else. You're not managing it through a separate Docker Compose file that lives outside of Proxmox's view.
Where I'd steer you away from it: anything that needs multiple cooperating containers (a web app plus a database plus a cache), anything you plan to live-migrate between cluster nodes regularly, or anything where you'd normally reach for Docker Compose. There's no compose equivalent here — each OCI image becomes one standalone container, full stop.
Prerequisites
Before you start, make sure you've got:
- Proxmox VE 9.1 or later. This feature doesn't exist on 9.0 or 8.x — check your version with
pveversionon the host's shell. - A storage location with the Container Template content type enabled. Directory-based storage (the default
localstorage) works out of the box. Pure block storage like an LVM-thin pool won't — it has nowhere to put template files. - Outbound internet access from the Proxmox host, since pulling an image means reaching out to Docker Hub or whichever registry you're using.
- Root access to the Proxmox web interface, or a user account with permission to create containers and pull templates.
- A little free disk space on that storage — a few hundred megabytes is plenty for something like nginx or a small Alpine-based app.
You don't need Docker installed anywhere. That's the whole point.
Step-by-Step Tutorial
Step 1: Confirm your Proxmox version
Open a shell on your Proxmox host — either through the >_ Shell button in the web UI on your node, or over SSH — and run:
pveversion
You're looking for something starting with pve-manager/9.1 or higher. If you're still on 9.0, run a full apt update && apt full-upgrade first and reboot before continuing. There's no point chasing an error caused by simply not having the feature yet.
Step 2: Pull an OCI image into storage
In the web GUI, click your node in the left-hand tree, then click the storage you want to use (for most single-node setups this is local). Open the CT Templates tab. You'll see a button labeled Pull from OCI Registry sitting next to the usual Templates button — that's new in 9.1.
Click it, and you'll get a dialog asking for a Reference. This is the image name, in the same format you'd use with docker pull. A few examples:
nginx:stable— pulls from Docker Hub by default if you don't specify a registry.docker.io/library/nginx:stable— the same image, written out in full.ghcr.io/someuser/someapp:latest— pulling from GitHub Container Registry instead.
Type in nginx:stable and hit Pull. Proxmox opens a task log window so you can watch it work — it's fetching layers from Docker Hub and converting them, so on a decent connection this takes somewhere around 15 to 30 seconds for something as small as nginx. Bigger images naturally take longer.
When it finishes, refresh the CT Templates list and you'll see a new entry, something like nginx-stable.tar, sitting alongside any regular templates you've already got.
Step 3: Create the container from the wizard
Click Create CT in the top-right corner, same as you would for any other container. Fill in the usual first page: a CT ID (Proxmox suggests the next free one), a Hostname, and a root password or SSH key. On the Template page, instead of picking a Debian or Ubuntu template, select the OCI template you just pulled — it'll be listed as nginx-stable under the storage you pulled it into.
Walk through the rest of the wizard as normal:
- Disks: 2–4 GB is plenty for something like nginx. You're not installing a full OS here.
- CPU: 1 core is fine for testing.
- Memory: 512 MB is generous for most small application images.
- Network: attach it to your usual bridge (typically
vmbr0) and let it grab an address over DHCP, or set a static one if that's how you normally run things.
Click through to Finish, and Proxmox creates and starts the container.
Step 4: Verify it's actually running
Since there's no systemd inside an application container, you can't just SSH in and run systemctl status nginx — there's no systemd to ask. Instead, check from the Proxmox side. Click on the container in the left tree and look at the Summary tab; if the single process inside exited immediately, you'll see the container listed as stopped even though you just started it, and that's your first clue something's wrong.
If it's showing as running, grab its IP address from the Summary page and test it directly:
curl http://<container-ip>
For the nginx example, you should get back the default "Welcome to nginx!" HTML page. That's confirmation the whole pipeline worked: pull, convert, boot, serve traffic.
Step 5 (optional): Do the same thing from the CLI
If you'd rather script this or you're doing it over SSH without the GUI open, the CLI path exists too. First pull the image via the API using pvesh:
pvesh create /nodes/pve/storage/local/oci-registry-pull --reference docker.io/library/nginx:stable
Replace pve with your actual node name (check it with hostname if you're not sure) and local with whichever storage you're pulling into. Once that finishes, the template shows up in storage the same way it would from the GUI, and you can create the container with the regular pct create syntax:
pct create 200 local:vztmpl/nginx-stable.tar \
--hostname nginx-oci \
--cores 1 \
--memory 512 \
--rootfs local-lvm:2 \
--net0 name=eth0,bridge=vmbr0,ip=dhcp \
--unprivileged 1
pct start 200
Nothing about that second command is special to OCI images — it's the exact same pct create syntax you'd use for a Debian template. That's kind of the point: once the image is on storage as a template, Proxmox stops caring where it came from.
Commands Explained
A quick rundown of what each piece above actually does, since it's easy to copy-paste commands without knowing what you just typed:
| Command / Flag | What it does |
|---|---|
pveversion | Prints the installed Proxmox VE version and package versions. Use it to confirm you're on 9.1+ before anything else. |
pvesh create .../oci-registry-pull | Calls the Proxmox API endpoint that pulls and converts an OCI image into a template, from the command line instead of the GUI button. |
pct create <id> <storage>:vztmpl/<file> | Creates a new LXC container using the specified template as its root filesystem. Works identically for OCI-derived templates and regular system templates. |
--rootfs local-lvm:2 | Allocates the container's root disk on the local-lvm storage, sized at 2 GB. |
--unprivileged 1 | Runs the container without root-equivalent access to the host kernel. Leave this on unless you have a specific reason not to — it's the safer default. |
--net0 name=eth0,bridge=vmbr0,ip=dhcp | Attaches a virtual network interface named eth0 to bridge vmbr0 and requests an IP address over DHCP. |
pct exec <id> -- <command> | Runs a one-off command inside the container from the host, without needing an interactive shell or SSH access. |
pct start / pct stop <id> | Starts or stops the container by its ID. |
Common Errors
A few things trip people up the first time they try this:
"toomanyrequests: You have reached your pull rate limit" — this is Docker Hub's anonymous-pull rate limit, not a Proxmox bug. Docker Hub caps how many image pulls an anonymous IP address can make per six hours. If you're testing repeatedly, you'll hit it eventually. Either wait it out, or authenticate your pulls with a free Docker Hub account.
The Pull from OCI Registry button doesn't show up at all — you're almost certainly on Proxmox VE 9.0 or older. This feature simply doesn't exist before 9.1. Check pveversion and upgrade if needed.
Container shows "running" but nothing responds on the expected port — a lot of Docker images are built assuming ports get mapped from outside (the way docker run -p 8080:80 works). LXC networking doesn't work that way. The container gets its own IP address on your bridge network, and the app's port is reachable directly at that IP, not through a mapped host port. Check what port the image actually listens on, and connect to <container-ip>:<that-port> instead of trying to hit the Proxmox host's own address.
"unable to create CT 200 - CT 200 already exists" — you picked a container ID that's already in use. Bump it to the next free number, or delete the old container first if it was just a leftover test.
Pull fails with a generic connection error — usually means your Proxmox host itself doesn't have working internet access, or a firewall rule between it and the outside world is blocking outbound HTTPS. Test with a plain curl -I https://registry-1.docker.io from the host shell before assuming the feature is broken.
Troubleshooting
If the container starts and then immediately stops, check the task log for that container's start action first — click the container, then Task History. Application containers run exactly one process, and if that process exits (crashes, or finishes and has nothing left to do), the container has nothing left running and stops itself. This is normal behavior for a single-process image, not necessarily a bug. Some images are designed to run a task and exit — those aren't good candidates for a long-running LXC container in the first place.
If you need to poke around inside a running application container to see what's going on, remember there's no full shell environment the way there is in a Debian container. Try:
pct exec 200 -- ls /
Some minimal images (particularly ones built on scratch or distroless bases) won't even have a shell binary inside them, so pct enter 200 will fail outright. That's an upstream image design choice, not something Proxmox is doing wrong — if you need to debug interactively, pick an image built on Alpine or Debian instead, at least while you're testing.
If networking looks fine everywhere else but this one container can't reach the internet, double check the --net0 line matches your actual bridge name. It's an easy typo to make if you've got more than one bridge configured (vmbr0 vs vmbr1), and the container will boot without complaint either way — it'll just sit there with no working route out.
Best Practices
Treat application containers as disposable. Since there's no in-place update path — you pull a new image tag and recreate the container rather than patching the running one — don't store anything important in an application container's local filesystem. If the app needs persistent data (a database, uploaded files), use a mount point pointing at separate storage, the same way you'd do it for a regular LXC container, and keep the container itself stateless.
Stick to small, single-purpose images. This feature shines for things like a reverse proxy, a static file server, or a small utility service. For anything with multiple moving parts, you're better off with a proper VM running Docker Compose, or several separate system containers each running one thing.
Pin your image tags. Pulling nginx:latest today and nginx:latest again in six months can get you two very different versions. Use a specific tag like nginx:1.27 so you know exactly what you're running and can reproduce it later.
Back these up like anything else. A vzdump backup job covers application containers the same way it covers system containers — there's nothing extra to configure. Don't skip it just because the container feels "lightweight."
Keep the technology-preview label in mind for anything you'd actually miss if it broke. I wouldn't put a production service on an application container yet, not because it's been unreliable in my experience, but because Proxmox reserves the right to change how this works before it's marked stable.
Frequently Asked Questions
Do I need Docker installed on my Proxmox host to use this?
No. That's the entire benefit. Proxmox pulls and converts the image itself using skopeo, with no Docker daemon involved anywhere in the process.
Can I run docker-compose.yml files this way?
No. Each OCI image becomes exactly one LXC container. If your setup needs multiple containers talking to each other, run Docker Compose inside a VM or a regular system container instead.
Will this replace Proxmox Community Scripts for installing apps?
Probably not for most people. Community Scripts install apps into full system containers with proper init systems, logging, and easy in-place updates. OCI application containers are a different tool for a narrower job — quick, disposable, single-process services.
Can I live-migrate an application container to another node?
Proxmox's own guidance is that application containers aren't the right choice when live migration and maximum isolation matter. If you need that, run the workload inside a VM instead.
What happens if I pull the same image tag twice?
You get a second template file in storage. Proxmox doesn't overwrite the existing one automatically, so you'll want to clean up old pulls yourself from the CT Templates tab if you're iterating on the same image repeatedly.
Is this feature available in Proxmox Backup Server or only Proxmox VE?
It's a Proxmox VE feature, specifically for creating LXC containers. Proxmox Backup Server backs up the resulting containers like any other guest, but it doesn't pull OCI images itself.
Conclusion
OCI image support in Proxmox VE 9.1 fills a genuinely annoying gap: what do you do with a project that only ships a Docker image? Before this, your options were running Docker inside a container (extra layer, extra overhead) or skipping the app entirely. Now you can pull the image straight in and let Proxmox turn it into a native container. It's not a replacement for Docker Compose, and it's not trying to be. Keep it for what it's good at — small, single-process services you want running without extra tooling in the way — and it'll earn its spot in your Proxmox setup. Just remember it's still a technology preview, so keep backups current and don't be surprised if the workflow shifts a little in future point releases.