How to Set Up Immich with ZFS on Proxmox (LXC)

Requirements

Before you begin, make sure the following prerequisites are met:

  • Proxmox VE host with a working ZFS pool
  • A ZFS dataset that will store the Immich data (example: data_nvme)
  • Debian 12 LXC template available on the Proxmox host
  • Support for unprivileged LXC containers
  • LXC features enabled: nesting, keyctl, fuse
  • At least 8 GB RAM for the container (more recommended depending on library size)
  • Network access for the container
  • Shell access to the Proxmox host

ZFS Dataset Layout

We will use the following ZFS dataset structure:

TEXT
/data_nvme
  └── immich
       ├── data
       └── database

The datasets data_nvme/immich, data_nvme/immich/data, and data_nvme/immich/database are created separately so they can use different ZFS properties. This allows tuning based on workload type.

Create the datasets in the Proxmox host shell:

BASH
zfs create data_nvme/immich
zfs create data_nvme/immich/data
zfs create data_nvme/immich/database

Verify the result:

BASH
zfs list

Example output:

BASH
NAME                        USED  AVAIL  REFER  MOUNTPOINT
data_nvme                   117M  3.51T   104K  /data_nvme
data_nvme/immich            296K  3.51T   104K  /data_nvme/immich
data_nvme/immich/data        96K  3.51T    96K  /data_nvme/immich/data
data_nvme/immich/database    96K  3.51T    96K  /data_nvme/immich/database

Why Separate ZFS Datasets?

The data and database directories are stored in separate ZFS datasets because they have different I/O patterns and tuning requirements.

Media dataset (data)

  • Large sequential files
  • Optimized with large recordsize (1M)
  • Strong compression (zstd)
  • Focus on throughput and storage efficiency

Database dataset (database)

  • Many small random reads and writes
  • Small recordsize (16K)
  • Low-latency tuning (logbias=latency)
  • Focus on IOPS and commit latency

Database Dataset (data_nvme/immich/database)

This dataset stores database files with many small random reads and writes. Recommended settings:

  • recordsize=16K
  • compression=lz4
  • atime=off
  • logbias=latency

Apply:

BASH
zfs set recordsize=16K data_nvme/immich/database
zfs set compression=lz4 data_nvme/immich/database
zfs set atime=off data_nvme/immich/database
zfs set logbias=latency data_nvme/immich/database

Warning: Adjust dataset paths if your pool name or mountpoints differ.

ZFS Ownership

Change ownership so the datasets belong to user data-share (UID/GID 1000):

BASH
chown -R data-share:data-share /data_nvme/immich

Example:

BASH
ls -al /data_nvme/immich/

Tip: If the user data-share does not yet exist, create it on the Proxmox host first. See: here

Create the LXC Container

BASH
pct create 104 local:vztmpl/debian-12-standard_12.12-1_amd64.tar.zst \
  --hostname immich \
  --cores 4 \
  --memory 8192 \
  --swap 1024 \
  --rootfs local-lvm:20 \
  --net0 name=eth0,bridge=vmbr0,ip=dhcp \
  --unprivileged 1 \
  --features nesting=1,keyctl=1,fuse=1 \
  --mp0 /data_nvme/immich/data,mp=/immich/data \
  --mp1 /data_nvme/immich/database,mp=/immich/database \
  --mp2 /data_nvme/user_data/immich,mp=/immich/data/library \
  --password 'YOUR_PASSWORD'

Adjust container ID, storage target, and template name to match your environment. Ensure the CT ID is not already in use.

Configure UID/GID Mapping

To ensure correct file ownership with bind-mounted datasets, configure custom UID/GID mappings.

Edit the container config on the Proxmox host:

BASH
nano /etc/pve/lxc/104.conf

Add:

TEXT
lxc.idmap: u 0 100000 1000
lxc.idmap: u 1000 1000 1
lxc.idmap: u 65534 165534 1
lxc.idmap: g 0 100000 1000
lxc.idmap: g 1000 1000 1
lxc.idmap: g 65534 165534 1

This maps UID/GID 1000 (data-share) 1:1 between host and container, while all other IDs remain remapped for security.

Reference: Unprivileged LXC containers

Configure Permissions Inside the Container

Create the data-share user and group inside the container with UID/GID 1000:

BASH
groupadd -g 1000 data-share
useradd -u 1000 -g 1000 -m -s /bin/bash data-share

Test access:

BASH
ls -al /immich

The user data-share should be able to write to /immich/data and /immich/database.

Install Docker Inside the Container

Start the container, update packages, and install Docker: Docker installation on Debian

Configure Docker Storage Driver (LXC)

In an unprivileged LXC container, the default Docker storage driver may not work correctly. Use fuse-overlayfs instead:

BASH
apt update
apt install -y fuse-overlayfs

mkdir -p /etc/docker

cat > /etc/docker/daemon.json << 'EOF'
{
  "storage-driver": "fuse-overlayfs"
}
EOF

systemctl restart docker

Install Immich

Follow the official Docker Compose installation guide: Immich installation

Before starting, edit the .env file:

TEXT
UPLOAD_LOCATION=/immich/data
DB_DATA_LOCATION=/immich/database

Start Immich:

BASH
docker compose up -d

First Login

Open the Immich web interface on port 2283.

Create your admin account and enable the storage template engine. Otherwise, you will not get a user-friendly folder structure for mounting in other VMs or containers.

Now you have a folder structure in /immich/data/. There is a /immich/data/uploads folder. You have so execute there commands in proxmox shell:

BASH
chmod g+s /data_nvme/immich/data/upload
chown data-share:data-share /data_nvme/immich/data/upload