Files
Main/2 Personal/Home Lab/NAS/immich_v1.1_setup.md
Obsidian-MBPM4 9420af83d6 vault backup: 2026-03-31 18:31:05
Affected files:
2 Personal/Home Lab/NAS/immich_v1.1_setup.md
2026-03-31 18:31:05 +02:00

791 lines
14 KiB
Markdown

# Immich V1 setup on Proxmox + Synology NAS + Authentik + Pangolin
## Status
This document is the **ground truth** for the current Immich V1 deployment and the first troubleshooting reference.
Current state:
- **Platform:** Proxmox VM
- **Guest OS:** Debian 13 server, headless
- **Networking:** LAN IP via DHCP reservation, plus ZeroTier installed
- **Container runtime:** Docker + Docker Compose
- **Immich deployment:** official Immich `docker-compose.yml`
- **Storage model:**
- **media/library on Synology NAS via NFS**
- **Postgres on local VM storage**
- **Planned next steps:** Authentik OIDC login, Pangolin public reverse proxy, Synology snapshots verification
---
## Why this architecture
This is the correct V1 shape because:
- Immich recommends a **full VM** in virtualized environments, not Docker in LXC.
- Immich recommends **Docker Compose** for normal deployment.
- Immich explicitly states that the **Postgres database should stay on local SSD storage and not on a network share**.
- Synology **Btrfs snapshots** are a good fit for the media share.
- NFS is a cleaner Linux-to-Linux storage mount than SMB for this use case.
---
## Current implemented architecture
```mermaid
flowchart LR
subgraph Users
U1[Browser users]
U2[Immich mobile app users]
U3[Admin via SSH]
end
subgraph Edge
ZT[ZeroTier]
PG[Pangolin - planned]
end
subgraph Proxmox
VM[Debian 13 VM\nimmich-vm]
DC[Docker Compose]
IS[Immich Server]
IML[Immich ML]
R[(Redis)]
DB[(Postgres\nlocal disk)]
CFG[/opt/immich-app\ncompose + .env/]
NFSM[/mnt/immich-prod\nNFS mount/]
end
subgraph Synology
SHARE[Shared folder: immich-prod]
SNAP[Snapshots - to configure/verify]
end
subgraph Identity
AK[Authentik - planned]
end
U3 --> ZT --> VM
U1 --> PG --> IS
U2 --> PG --> IS
VM --> DC
DC --> IS
DC --> IML
DC --> R
DC --> DB
IS --> NFSM
IML --> NFSM
NFSM --> SHARE
SHARE --> SNAP
IS --> AK
```
---
## VM build decisions
### Guest type
- **Debian 13 server/headless**
- No desktop environment
- SSH server installed
- Standard system utilities installed
### Proxmox VM settings used/recommended
- **Machine type:** `q35`
- **BIOS:** `OVMF (UEFI)`
- **Graphics:** default / minimal, no desktop needed
- **CPU type:** ideally `host`
- acceptable fallback: `x86-64-v2-AES`
- **vCPU:** 4
- **RAM:** 8 GB recommended
- **Disk:** local SSD-backed VM disk, enough for OS + Docker + Postgres
- good V1 default: **64 GB**
- **NIC model:** VirtIO
### Why
- `q35` + `OVMF` is the modern sane default.
- Debian headless keeps the VM simple and low-maintenance.
- Immich itself does not need a GUI on the host.
- Local disk is used for DB because the DB must **not** live on NFS.
---
## Directory layout
### On the VM
```text
/opt/immich-app/
├── docker-compose.yml
├── .env
└── postgres/
/mnt/immich-prod/
├── library/
└── model-cache/ # optional, if you keep ML cache here
```
### Why this layout
- `/opt/immich-app` is for the **application deployment**, not user files.
- `/mnt/immich-prod` is the mounted NAS share.
- `postgres/` stays on **local VM storage**.
- Do **not** put the project under `/home/cef/...` for production-style operation.
---
## Installed packages / components
Installed on the VM:
- `docker`
- `docker compose`
- `zerotier`
- `nfs-common`
- `sudo`
Useful verification commands:
```bash
docker --version
docker compose version
zerotier-cli info
showmount -e 192.168.1.34
mount | grep immich
```
---
## Synology NFS setup
### NAS
- **Synology IP:** `192.168.1.34`
- **Shared folder / export:** `/volume1/immich-prod`
- **Allowed client:** `192.168.1.52`
Verified export list:
```bash
sudo showmount -e 192.168.1.34
```
Expected output:
```text
Export list for 192.168.1.34:
/volume1/Downloads 192.168.1.35/24
/volume1/immich-prod 192.168.1.52
```
### VM mountpoint
```bash
sudo mkdir -p /mnt/immich-prod
```
### Manual mount command
```bash
sudo mount -t nfs 192.168.1.34:/volume1/immich-prod /mnt/immich-prod
```
### Important typo that already happened once
Wrong:
```bash
sudo mount -t nfs 192.168.1.34:/volumel/immich-prod /mnt/immich-prod
```
Correct:
```bash
sudo mount -t nfs 192.168.1.34:/volume1/immich-prod /mnt/immich-prod
```
The mistake was **`volumel`** with letter `l` instead of **`volume1`** with number `1`.
### Recommended persistent mount in `/etc/fstab`
Use this:
```fstab
192.168.1.34:/volume1/immich-prod /mnt/immich-prod nfs rw,hard,_netdev,x-systemd.automount,noatime 0 0
```
Then test it:
```bash
sudo mount -a
mount | grep immich-prod
df -h | grep immich-prod
```
### Why these mount options
- `rw` -> read/write
- `hard` -> keep retrying if the NAS drops briefly
- `_netdev` -> network-dependent mount
- `x-systemd.automount` -> avoids ugly boot timing issues
- `noatime` -> reduces metadata writes
---
## Immich deployment files
### Project directory
```bash
cd /opt/immich-app
```
### Compose file
Use the **official Immich release** `docker-compose.yml` unchanged unless there is a specific reason to change it.
This is important because:
- the official file stays aligned with the current release
- random blog versions drift
- hand-written compose files become future maintenance debt
### Current `.env`
Current deployment values:
```dotenv
# You can find documentation for all the supported env variables at https://docs.immich.app/install/environment-variables
# The location where your uploaded files are stored
UPLOAD_LOCATION=/mnt/immich-prod/library
# The location where your database files are stored
# MUST stay on local VM storage, not on NFS
DB_DATA_LOCATION=/opt/immich-app/postgres
# Set your timezone
TZ=Europe/Zurich
# Immich Settings
IMMICH_ENV=production
# Immich version
IMMICH_VERSION=v2
# Database credentials
# Use only A-Za-z0-9 for this value
DB_PASSWORD=my-secret-pw. # See Lastpass
# Usually leave these as default unless you have a reason to change them
DB_USERNAME=postgres
DB_DATABASE_NAME=immich
```
### Why these values are correct
- `UPLOAD_LOCATION=/mnt/immich-prod/library`
- correct because media belongs on the NAS share
- `DB_DATA_LOCATION=/opt/immich-app/postgres`
- correct because DB must stay local
- `TZ=Europe/Zurich`
- good operational default
- `IMMICH_ENV=production`
- correct for this VM
- `IMMICH_VERSION=v2`
- matches current official release convention
---
## Useful Docker commands
### Start / recreate stack
```bash
cd /opt/immich-app
docker compose up -d
```
### Stop stack
```bash
cd /opt/immich-app
docker compose down
```
### See running containers
```bash
docker ps
```
### Follow logs
```bash
cd /opt/immich-app
docker compose logs -f
```
### Follow a single service log
```bash
docker compose logs -f immich-server
docker compose logs -f database
docker compose logs -f redis
docker compose logs -f immich-machine-learning
```
### Restart stack
```bash
docker compose restart
```
### Pull updated images later
```bash
cd /opt/immich-app
docker compose pull
docker compose up -d
```
---
## Current data model
### Media
Stored on NAS via:
```text
/mnt/immich-prod/library
```
This means:
- media is on Synology storage
- Synology snapshots can protect it
- the actual photos/videos do **not** live on the VM disk
### Database
Stored locally via:
```text
/opt/immich-app/postgres
```
This means:
- DB is not exposed to NFS consistency problems
- VM backup strategy must include this path
- DB and media are separate backup concerns
### Automatic Immich DB backups
Immich also stores automatic DB dumps under the upload location, typically in:
```text
UPLOAD_LOCATION/backups
```
Those backups contain **metadata only**, not the photo/video files.
So:
- NAS snapshots help protect media and DB dump files
- but media + DB are still two separate pieces of the system
---
## Synology snapshots
### Goal
The `immich-prod` shared folder should have Synology snapshots enabled.
### Why
This gives:
- fast rollback after accidental deletion
- protection against bad imports or user mistakes
- short-term recovery without touching full backups
### Good V1 retention suggestion
- hourly snapshots for 24 hours
- daily snapshots for 14 days
- weekly snapshots for 8 weeks
### Important truth
Snapshots are **not enough by themselves**.
They are rollback protection, not the full backup strategy.
The real backup picture later must include:
- Synology media share backup offsite
- VM / Postgres backup
- restore testing
---
## Authentik plan
Planned next phase:
- configure Immich login through **Authentik OIDC**
- keep local Immich login enabled until OIDC is proven working
### Important future redirect URIs
When creating the Immich OIDC application/provider in Authentik, include:
```text
app.immich:///oauth-callback
https://immich.<your-domain>/auth/login
https://immich.<your-domain>/user-settings
```
The mobile callback is required for app login.
### Safe rollout rule
Do this in order:
1. verify local Immich admin login works
2. configure Authentik OIDC
3. test browser login
4. test mobile login
5. only then consider disabling password login
---
## Pangolin plan
Planned next phase:
- expose Immich publicly through **Pangolin**
- do **not** expose port `2283` directly to the internet
### Reverse proxy requirements for Immich
The reverse proxy must correctly pass:
- `Host`
- `X-Real-IP`
- `X-Forwarded-Proto`
- `X-Forwarded-For`
It also must:
- allow **large uploads**
- serve Immich on the **root of a subdomain**, not a sub-path
Correct:
```text
https://immich.example.com
```
Wrong:
```text
https://example.com/immich
```
---
## Networking notes
### DHCP reservation issue already seen
The VM originally still held the old IP lease after a router DHCP reservation change.
Fastest fix was simply:
```bash
su -
reboot
```
Reason:
- DHCP reservation does not always force immediate address change
- the client often keeps the old lease until renew/reboot/expiry
### Current relevant IPs
- **Synology NAS:** `192.168.1.34`
- **Immich VM:** `192.168.1.52`
---
## Troubleshooting logbook
### Problem: `sudo: command not found`
Cause:
- user did not yet have sudo available / configured
Fix:
```bash
su -
apt update
apt install sudo
usermod -aG sudo cef
reboot
```
---
### Problem: `apt update` permission denied / lock file errors
Cause:
- command was run as non-root user without sudo
Fix:
```bash
su -
apt update
```
---
### Problem: `showmount: command not found`
Cause:
- NFS client tools not installed yet
Fix:
```bash
sudo apt update
sudo apt install -y nfs-common
```
Then:
```bash
sudo showmount -e 192.168.1.34
```
---
### Problem: `mkdir: cannot create directory '/mnt/immich-prod': Permission denied`
Cause:
- `/mnt` requires root privileges
Fix:
```bash
sudo mkdir -p /mnt/immich-prod
```
---
### Problem: `mount.nfs: access denied by server while mounting ...`
Actual cause in this case:
- typo in mount source path: used `volumel` instead of `volume1`
Correct command:
```bash
sudo mount -t nfs 192.168.1.34:/volume1/immich-prod /mnt/immich-prod
```
If it happens again with the correct path, then check:
1. Synology NFS service enabled
2. Synology export path correct
3. Synology NFS permissions allow `192.168.1.52`
4. VM actually has IP `192.168.1.52`
5. export visible via `showmount -e 192.168.1.34`
---
### Problem: changed router DHCP reservation but VM kept old IP
Cause:
- client kept existing lease
Fix:
```bash
su -
reboot
```
Alternative debugging commands:
```bash
ip a
networkctl
systemctl status systemd-networkd
```
---
### Problem: hidden `.env` file does not show in normal `ls`
Cause:
- dotfiles are hidden by default
Fix:
```bash
ls -la /opt/immich-app
```
---
## Operational rules
### Rules to keep
1. **Never move Postgres onto the NAS share.**
2. **Keep using the official Immich compose file.**
3. **Do not improvise custom Dockerfiles for V1.**
4. **Do not expose raw Immich directly to the internet.** Use Pangolin.
5. **Do not disable local login until Authentik login is proven working.**
6. **Keep the Synology shared folder dedicated to Immich.**
7. **Treat snapshots as rollback, not as the only backup.**
### Rules for changes later
If changing `.env` or updating Immich:
```bash
cd /opt/immich-app
docker compose pull
docker compose up -d
```
If something acts weird after env changes:
```bash
docker compose up -d --force-recreate
```
---
## What still needs to be done
### Must do next
1. verify `/etc/fstab` persistent NFS mount works across reboot
2. verify Synology snapshots are enabled on `immich-prod`
3. configure Authentik OIDC
4. configure Pangolin public access
5. test large upload through the reverse proxy
6. test Immich mobile login through OIDC
### Should do soon
1. rotate the DB password because it was shared in chat
2. document exact Pangolin config once implemented
3. document exact Authentik provider/app config once implemented
4. create first restore notes for:
- Immich DB
- media share
- full VM restore
---
## Fast command reference
### Check mount
```bash
mount | grep immich
df -h | grep immich
ls -la /mnt/immich-prod
```
### Check Docker
```bash
docker ps
docker compose logs -f
```
### Restart Immich
```bash
cd /opt/immich-app
docker compose restart
```
### Full stack recreate
```bash
cd /opt/immich-app
docker compose down
docker compose up -d
```
### Reboot VM
```bash
sudo reboot
```
---
## Final blunt summary
The current setup is on the right track.
The important good decisions already made are:
- full VM instead of LXC
- Debian server instead of desktop bloat
- official Immich compose
- media on NAS via NFS
- Postgres on local storage
- clear deployment path under `/opt/immich-app`
The main things that can still hurt later are:
- forgetting to persist and verify the NFS mount properly
- forgetting to enable/test Synology snapshots
- breaking login while introducing Authentik
- exposing Immich badly when adding Pangolin
- leaving the documented DB password unchanged
This document should be updated after every meaningful change.
---
```mermaid
flowchart LR
User[User Browser] -->|HTTPS| Cloudflare[Cloudflare Edge]
Cloudflare -->|Tunnel| CFD[cloudflared in edge stack]
CFD -->|HTTP or HTTPS, but one consistent model| Traefik[Traefik]
Traefik -->|Badger auth check| Pangolin[Pangolin]
Traefik -->|Proxy via Gerbil/Newt| RemoteApps[Apps behind Pangolin]
Cloudflare -->|Direct route| Authentik[Authentik direct]
Pangolin --> Gerbil[Gerbil]
Newt[Newt on remote hosts] --> Pangolin
Newt --> Gerbil
```