Skip to content

Instantly share code, notes, and snippets.

@potat-dev
Created May 15, 2026 17:40
Show Gist options
  • Select an option

  • Save potat-dev/0e1df150a57bfb3ef0dec750a7f67086 to your computer and use it in GitHub Desktop.

Select an option

Save potat-dev/0e1df150a57bfb3ef0dec750a7f67086 to your computer and use it in GitHub Desktop.
Immich LXC ZFS idmap configuration

Since you are comfortable with the configuration and want a professional, proper systems administration approach, mitigating the security risk of idmap is straightforward.

The security risk exists only if you map the container to a privileged or shared host user (like your primary personal account, UID 1000, which might have sudo access or own sensitive host configurations). If a bad actor breaks out of the container via a Docker/LXC exploit, they land on the host with the permissions of that mapped UID.

To completely mitigate this risk, you create a "Black Hole" or "Empty Room" configuration. You punch a hole through the LXC isolation, but you make sure the hole leads to a user that has absolutely zero power on the host system.

Here are the exact methods to lock this down:

1. The "Dedicated Dead User" Strategy (Primary Mitigation)

Do not map the LXC container to UID 1000. Create a dedicated, unprivileged system account on the Proxmox host that exists solely to own this dataset.

On the Proxmox Host:

# Create a dedicated group and user (e.g., UID/GID 2500)
# -M: No home directory
# -s /usr/sbin/nologin: Cannot log into a shell
groupadd -g 2500 immich_storage
useradd -u 2500 -g 2500 -M -s /usr/sbin/nologin immich_storage

# Chown the ZFS dataset to this dead user
chown -R 2500:2500 /tank/immich_data

Why this works: If the container is compromised and the attacker executes a container escape as UID 2500, they land on the host as immich_storage. This user has no shell, no home directory, belongs to no privileged groups, and owns absolutely nothing on the host except the photos they already compromised anyway. The isolation is practically maintained.

The LXC Math for UID 2500: Your /etc/pve/lxc/XXX.conf would look like this:

lxc.idmap: u 0 100000 2500
lxc.idmap: g 0 100000 2500
lxc.idmap: u 2500 2500 1
lxc.idmap: g 2500 2500 1
lxc.idmap: u 2501 102501 63035
lxc.idmap: g 2501 102501 63035

And your host /etc/subuid and /etc/subgid gets:

root:2500:1

2. Lock Down the ZFS Dataset Properties

Since you are using ZFS, you have an incredibly powerful security layer at the filesystem level. You can instruct the host kernel to completely neuter what can be done with files residing on that specific dataset.

On the Proxmox Host:

zfs set setuid=off tank/immich_data
zfs set devices=off tank/immich_data
zfs set exec=off tank/immich_data

Why this works:

  • exec=off: If an attacker manages to upload a malicious binary or script into your Immich library via a web vulnerability, the host kernel will flat-out refuse to execute it from that directory.
  • setuid=off: Prevents the creation of SUID binaries in the dataset, effectively destroying a common privilege escalation vector.
  • devices=off: Prevents the creation of device nodes (like fake /dev/sda files) which is a common trick used in container escape attacks.

3. Enforce User Namespaces in Docker Compose

By default, Docker containers run their internal processes as root (UID 0 inside the container, which maps to UID 100000 in your LXC).

To cleanly align Docker with your idmap, you instruct Docker to run the Immich services explicitly as your mapped user (2500).

In your docker-compose.yml, you can pass the user context to the Immich server and machine learning containers:

services:
  immich-server:
    container_name: immich_server
    image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
    user: "2500:2500" # Force process to run as our mapped UID/GID
    # ... rest of config

(Note: Be careful with the PostgreSQL and Redis containers, as they often require initializing their internal directories as root before dropping to their own UIDs. For Immich's main data volume, setting the user on immich-server and immich-machine-learning is usually sufficient).

4. Maintain LXC AppArmor Confinement

Because you need to enable Nesting to run Docker in the LXC, Proxmox slightly relaxes AppArmor profiles to allow Docker to create its own namespaces. Do not be tempted to check the "Privileged container" box or turn off AppArmor completely to troubleshoot Docker issues. Keep the container Unprivileged, leave AppArmor enabled (the default for Proxmox LXCs), and only enable the specific features needed: Nesting and (if Postgres complains) keyctl.

Summary

By mapping the LXC to a dedicated nologin user and applying ZFS noexec/nosuid flags, you completely neutralize the risks of idmap. You get exactly what you want: a perfectly clean Proxmox host filesystem with native UIDs, without compromising the hypervisor's security.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment