homelab-blueprint/docker-compose-files/immich
Brian Pooe 1815beadf4 refactor: restructure docs, sync stacks with Caddyfile, harden compose templates
- Add compose stacks for apps already proxied by Caddy: bento-pdf,
  it-tools, paperless-ngx, and beszel-hub (with Makefile targets)
- Remove the unused Vault stack and the drawio Caddyfile block
- Fix Caddyfile gramps upstream to port 80 to match the compose file
- Bring immich and gramps-web up to the shared template pattern
  (no-new-privileges, log rotation, memory limits, healthchecks)
- Single-quote vaultwarden SSO_AUDIENCE_TRUSTED so regex values like
  ^\d{18}$ render as valid YAML
- Move root docs into kebab-case topic folders, rebuild the docs index
  to cover every doc, and fix all broken links
2026-07-03 17:45:16 +02:00
..
.env.sample chore: track .env.sample files for all stacks 2026-07-02 22:28:44 +02:00
README.md docs(immich): add phone-to-NAS mobile sync guide and wire mobile_photos share 2026-06-27 15:31:08 +02:00
template.yaml refactor: restructure docs, sync stacks with Caddyfile, harden compose templates 2026-07-03 17:45:16 +02:00

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.