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

132 lines
5.7 KiB
Markdown

# 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:
```dotenv
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.
```dotenv
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:
```bash
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:
```bash
# 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**:
```bash
./substitute_env.sh docker-compose-files/immich/template.yaml docker-compose.immich.yml .env
```
5. **Start Stack**:
```bash
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):
```bash
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:
```bash
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:
```bash
# 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.