homelab-blueprint/docker-compose-files/immich/README.md
Brian Pooe 86cfe2aecc docs(immich): add phone-to-NAS mobile sync guide and wire mobile_photos share
Documents the FolderSync/PhotoSync → UNAS Pro → Immich external library
pattern as the workaround for immich-app/immich#4282 (deleted photos
re-uploading on next mobile sync). Includes all steps: UNAS share creation,
Proxmox NFS mount, LXC bind mount, docker-compose volume, Immich library
import path update, and per-app config for Android (FolderSync) and iOS
(PhotoSync).

- Adds docs/immich/mobile-sync.md with full step-by-step guide
- Adds mobile_photos to share map in docs/unas/nfs-mounts.md
- Adds IMMICH_MOBILE_SYNC_DIR volume (/mnt/mobile:ro) to template.yaml
- Documents IMMICH_MOBILE_SYNC_DIR in README.md and .env.sample
2026-06-27 15:31:08 +02:00

5.7 KiB

Immich

Self-hosted photo and video management solution with high performance and visual aesthetics.

Prerequisites

  • NVIDIA GPU: This template is configured for NVIDIA GPU acceleration (RTX A400).
  • Proxmox LXC: Must be a Privileged LXC with NVIDIA drivers and Container Toolkit installed.
  • Storage: NFS-backed storage for photo uploads is recommended.

Deployment

  1. Update .env: Ensure the Immich variables are set in your root .env file. IMMICH_UPLOAD_LOCATION should point to your dedicated Immich share on the UNAS:

    IMMICH_UPLOAD_LOCATION=/mnt/unas/immich_data
    IMMICH_DATA_DIR=/opt/immich/appdata
    IMMICH_EXTERNAL_LIBRARY=/mnt/unas/immich_data/upload/upload
    IMMICH_MOBILE_SYNC_DIR=/mnt/unas/mobile_photos
    IMMICH_VERSION=release
    IMMICH_DB_PASSWORD=<your-secure-password>
    IMMICH_DB_USERNAME=immich
    IMMICH_DB_DATABASE_NAME=immich
    IMMICH_DB_HOSTNAME=database
    IMMICH_REDIS_HOSTNAME=redis
    

    Optional OIDC settings can also be supplied. Set OIDC_ENABLED=true explicitly when enabling OAuth; leaving it unset keeps Immich local-login only. Zitadel with PKCE does not require a client secret — leave OIDC_CLIENT_SECRET empty.

    OIDC_ENABLED=true
    OIDC_ISSUER=https://zitadel.example.com/
    OIDC_CLIENT_ID=<immich-client-id>
    OIDC_CLIENT_SECRET=
    OIDC_NAME=<button-label>
    OIDC_AUTO_REDIRECT=false
    OIDC_MOBILE_OVERRIDE_ENABLED=false
    OIDC_DISABLE_LOCAL_AUTH=false
    

    Once OIDC is confirmed working, set OIDC_DISABLE_LOCAL_AUTH=true and restart the server to enforce SSO-only login:

    sed -i 's/DISABLE_PASSWORD_LOGIN=false/DISABLE_PASSWORD_LOGIN=true/' /opt/immich/docker-compose.yml
    docker compose -f /opt/immich/docker-compose.yml up -d --no-deps --force-recreate immich-server
    
  2. Create Directories: Create the local appdata directories and the shared media directory:

    # Local config/database storage (matches IMMICH_DATA_DIR)
    sudo mkdir -p /opt/immich/appdata/{postgres,model-cache}
    sudo chown -R 1000:1000 /opt/immich/appdata
    
    # Separate UNAS share for photos
    sudo mkdir -p /mnt/unas/immich_data
    sudo chown -R 1000:1000 /mnt/unas/immich_data
    
  3. Mounting the Share: Ensure you have created a separate NFS share on your UNAS (e.g., immich_data) and mounted it on your Proxmox host at /mnt/unas/immich_data before starting the containers.

  4. Render Compose File:

    ./substitute_env.sh docker-compose-files/immich/template.yaml docker-compose.immich.yml .env
    
  5. Start Stack:

    docker compose -f docker-compose.immich.yml up -d
    

External Library Setup

After the stack is running, create an external library via the API to import photos from IMMICH_EXTERNAL_LIBRARY (mounted read-only at /mnt/external inside the container):

TOKEN=$(curl -s -X POST http://<immich-ip>:2283/api/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"<email>","password":"<password>"}' \
  | python3 -c 'import sys,json; print(json.load(sys.stdin)["accessToken"])')

USER_ID=$(curl -s http://<immich-ip>:2283/api/users/me \
  -H "Authorization: Bearer $TOKEN" \
  | python3 -c 'import sys,json; print(json.load(sys.stdin)["id"])')

LIB_ID=$(curl -s -X POST http://<immich-ip>:2283/api/libraries \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $TOKEN" \
  -d "{\"name\":\"NAS Photos\",\"ownerId\":\"$USER_ID\",\"importPaths\":[\"/mnt/external/<user-uuid>\"]}" \
  | python3 -c 'import sys,json; print(json.load(sys.stdin)["id"])')

curl -s -X POST http://<immich-ip>:2283/api/libraries/$LIB_ID/scan \
  -H "Authorization: Bearer $TOKEN"

The <user-uuid> is the Immich user's UUID — find it at Settings → Account in the Immich UI, or via:

curl -s http://<immich-ip>:2283/api/users/me -H "Authorization: Bearer $TOKEN" | python3 -c 'import sys,json; print(json.load(sys.stdin)["id"])'

Important

Immich rejects any external library import path that falls under /data/upload/ (the upload volume). The separate /mnt/external mount exists specifically to work around this restriction — the same photos are accessible at both paths inside the container, but only /mnt/external is accepted for external libraries.

Privileged Mode Rationale

Immich is deployed in a Privileged LXC to ensure:

  • GPU Access: The immich-machine-learning container requires direct access to NVIDIA device nodes for CUDA compute.
  • VRAM Management: CUDA memory allocation and GSP firmware communication are more stable in privileged mode.
  • Storage Mapping: Simplified 1:1 UID/GID mapping for photo uploads on the UNAS NFS share.

GPU Verification

Both immich-server and immich-machine-learning run with runtime: nvidia:

  • immich-server — uses NVENC for hardware-accelerated H.264 video transcoding (NVIDIA_DRIVER_CAPABILITIES=video,compute,utility)
  • immich-machine-learning — uses the :release-cuda image with CUDAExecutionProvider for CLIP embeddings, face detection, and OCR

Verify GPU access after startup:

# Confirm NVIDIA device visible in server (needed for NVENC)
docker exec immich_server ls /dev/nvidia0

# Confirm CUDA ONNX provider active in ML container
docker exec immich_machine_learning python3 -c 'import onnxruntime; print(onnxruntime.get_available_providers())'
# Expected: ['TensorrtExecutionProvider', 'CUDAExecutionProvider', 'CPUExecutionProvider']

After verifying, go to Administration → Video Transcoding in the Immich UI and confirm Hardware Acceleration is set to NVENC. If it shows QSV (from a prior config), change it to NVENC and re-run the Video Conversion job.