Skip to content

Proxmox ​

camera.ui runs on Proxmox VE inside an LXC container that hosts Docker. One command on the Proxmox host sets up everything: the container, Docker, and camera.ui.

Why an LXC and not a VM?

An LXC can share the iGPU: /dev/dri stays available to the host and other containers at the same time, while a VM with PCIe passthrough monopolizes the GPU, and an LXC has less overhead. A VM is the better choice for NVIDIA cards (see below).

Quick start ​

Run this on the Proxmox host (as root):

bash
curl -fsSLO https://raw.githubusercontent.com/cameraui/docker/main/proxmox/install-cameraui-lxc.sh
bash install-cameraui-lxc.sh

The script downloads the Ubuntu 24.04 LXC template, creates an unprivileged container with nesting (privileged containers can no longer load Docker's AppArmor profile on current Proxmox releases), installs Docker inside it, deploys camera.ui via Docker Compose and prints the URL of the web UI. Tested with Proxmox VE 9.

Environment variables:

VariableDefaultMeaning
CTIDnext free IDcontainer ID
CT_HOSTNAMEcamerauicontainer hostname
CORES / RAM_MB / DISK_GB4 / 4096 / 16container resources
BRIDGEvmbr0network bridge (container gets its IP via DHCP)
STORAGEauto-detectrootfs storage (prefers local-lvm, local-zfs, local, otherwise the first active storage that can hold a container rootfs)
TEMPLATE_STORAGElocalstorage the LXC template is downloaded to (needs content type "Container template")
FLAVORcpucpu, intel or amd (picks the image flavor); nvidia is experimental
GPU_PASSTHROUGH1 when flavor ≠ cpupass /dev/dri into the container
TZhost timezonetimezone inside the container

Example for an Intel machine: FLAVOR=intel bash install-cameraui-lxc.sh

Getting a shell in the container ​

The script creates the container without a root password, so the Proxmox console and SSH won't let you in. From the host shell:

bash
pct enter <CTID>

Single commands:

bash
pct exec <CTID> -- docker logs cameraui

Console and SSH work once you set a password:

bash
pct exec <CTID> -- passwd

Hardware acceleration ​

  • Intel / AMD iGPU. Set FLAVOR=intel (or amd). The script passes the GPU render node through the container into Docker, nothing else to configure. Details: Hardware acceleration.
  • NVIDIA. Use a VM with PCIe passthrough instead of an LXC. Inside it, follow the standard Docker setup with the nvidia flavor. An LXC would need the host driver and the container's user-space libraries in permanent version lockstep. The VM does monopolize the card. If other services on the host need the same GPU (Ollama, Jellyfin), see NVIDIA in an LXC below.
  • AI accelerators (Coral, Hailo). Install the host driver on the Proxmox host, then pass the device node (/dev/apex_0, /dev/hailo0) into the container.

NVIDIA in an LXC (experimental) ​

FLAVOR=nvidia shares the card between the container and other services on the host, which a VM cannot do. The catch is a hard version coupling: the NVIDIA user-space libraries inside the container must exactly match the host's kernel driver, after every driver update.

It needs a working NVIDIA driver on the Proxmox host first (.run installer with --dkms plus pve-headers; nvidia-smi on the host must work), and the installed driver version must exist on download.nvidia.com. Then:

bash
FLAVOR=nvidia bash install-cameraui-lxc.sh

The script passes all /dev/nvidia* device nodes into the container, installs the matching user-space driver (--no-kernel-module) plus the NVIDIA Container Toolkit (with no-cgroups=true, because an unprivileged container cannot manage device cgroups), and registers a boot-time sync service that re-installs the matching user space after host driver updates. Verify with:

bash
pct exec <CTID> -- docker exec cameraui nvidia-smi

Experimental

This path depends on NVIDIA's installer archive and on the host driver staying healthy; after a host driver update the container needs a reboot to re-sync. If it breaks, the VM route always works. Reports and fixes are welcome in the docker repo.

Recordings on a dedicated disk or NAS ​

Recordings default to the container's own volume. For a bigger disk, NAS share or merged pool, hand that storage into the container and set the NVR's storage path to it.

Unprivileged containers cannot mount NFS/CIFS shares themselves. Mount the share on the Proxmox host and hand it into the container as a mount point:

bash
pct set <CTID> -mp0 /mnt/nas/recordings,mp=/mnt/recordings

Then set the NVR Storage Path to /mnt/recordings (Settings → Recordings → Storage), or see the storage section of the Docker page.

FUSE mounts (mergerfs, rclone) need extra care

A FUSE mount (such as a mergerfs pool merging several drives) does not propagate into an (unprivileged) container the way a normal filesystem does. If the FUSE mount isn't active on the host before the container starts, the container sees the empty mountpoint on the host's root disk instead of the pool. Recordings then land on the small OS disk and get rotated away within minutes.

Mount the pool on the host before the container starts (e.g. via an fstab entry or a systemd unit ordered Before=pve-container@<CTID>.service), and make sure the mount is shared into the container. Then always verify from inside the container.

Verify your storage size ​

After setting a custom storage path, check that the container sees the full volume:

bash
pct exec <CTID> -- df -h /mnt/recordings

The reported size must match your real disk or pool. The small container root size means the mount didn't propagate. The NVR also logs its resolved storage and size on every start, and shows a warning in Settings → Recordings when the volume is unexpectedly small.

Verify ​

On every start the container logs which devices actually arrived. See Verify from the container. The camera.ui UI is available at https://<container-ip>:3443 (the script prints the IP at the end).