vault backup: 2026-03-31 14:56:35
Affected files: .obsidian/workspace.json 2 Personal/Home Lab/NAS/immich_v1.1_setup.md
This commit is contained in:
771
2 Personal/Home Lab/NAS/immich_v1.1_setup.md
Normal file
771
2 Personal/Home Lab/NAS/immich_v1.1_setup.md
Normal file
@@ -0,0 +1,771 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user