Immich Photo Backup¶
Overview¶
Immich is a self-hosted, high-performance photo and video backup solution that aims to directly replace Google Photos. Unlike traditional file managers, Immich relies heavily on machine learning to provide features like facial recognition, object detection (e.g., "search for 'cat'"), and map-based exploration.
The architecture is microservice-based, meaning it runs several distinct containers (Server, Machine Learning, Postgres, Valkey/Redis) that work in concert to handle massive upload queues and background processing tasks. Because the machine-learning container talks to the server over HTTP, it doesn't have to run on the same machine.
How I run it¶
Immich is split across two hosts:
| Host | Containers | Why there |
|---|---|---|
nas |
immich-server, Postgres (immich-app/postgres with VectorChord), Valkey |
The photo library lives on the NAS pool, so the server writes uploads straight to local disk. Postgres sits on the NAS's SSD, not the pool. |
server2 |
immich-machine-learning (-openvino image) |
Face detection, CLIP and smart search are the CPU-heavy part. The NAS has an older i5-8500T and already carries Plex and Frigate; the apps box has a newer i5-12500T with a UHD 770 iGPU and plenty of headroom, so ML runs there with OpenVINO. |
The server finds ML through IMMICH_MACHINE_LEARNING_URL. Uploads, thumbnails and video transcoding (Quick Sync on the
NAS's own iGPU) stay on the NAS; only image previews travel to the ML host.
Server and ML must run the same version
Both images are pinned to the same tag and bumped together, by hand, in one commit. Renovate groups
immich-server and immich-machine-learning into one PR that is never automerged. A mismatched pair causes odd
bugs (failed jobs, wrong embeddings), see the remote ML guide.
Installation¶
Both stacks start from the official release files: docker-compose.yml, hwaccel.transcoding.yml (server side) and
hwaccel.ml.yml (ML side). Keep the hwaccel file next to the compose file that extends it:
# on the NAS (server stack)
wget https://github.com/immich-app/immich/releases/latest/download/hwaccel.transcoding.yml
# on the ML host
wget https://github.com/immich-app/immich/releases/latest/download/hwaccel.ml.yml
Server, database and Valkey (NAS)¶
Hardware note
The NAS has an Intel i5-8500T and no discrete GPU, so transcoding uses service: quicksync (it just passes
/dev/dri into the container). With an Nvidia card use nvenc instead.
name: immich
services:
immich-server:
container_name: immich_server
image: ghcr.io/immich-app/immich-server:v3.3.5
extends:
file: hwaccel.transcoding.yml
service: quicksync # Intel iGPU; nvenc / vaapi / rkmpp for other hardware
volumes:
- ${UPLOAD_LOCATION}:/data
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
environment:
# ML runs on another host; must be the same Immich version as this server
- IMMICH_MACHINE_LEARNING_URL=http://<ML_HOST_IP>:3003
ports:
- '2283:2283'
depends_on:
- redis
- database
restart: always
healthcheck:
disable: false
redis:
container_name: immich_redis
image: docker.io/valkey/valkey:9@sha256:418652cfb58ef879d4978c33553735d7147016032d5aefaa14c828e611eb9dfd
healthcheck:
test: redis-cli ping || exit 1
restart: always
database:
container_name: immich_postgres
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:bcf63357191b76a916ae5eb93464d65c07511da41e3bf7a8416db519b40b1c23
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_DB: ${DB_DATABASE_NAME}
POSTGRES_INITDB_ARGS: '--data-checksums'
# DB_STORAGE_TYPE: 'HDD' # only if the database is NOT on an SSD
volumes:
- ${DB_DATA_LOCATION}:/var/lib/postgresql/data
shm_size: 128mb
restart: always
# Photo library on the pool, Postgres on SSD
UPLOAD_LOCATION=/mnt/storage/photos/immich/library
DB_DATA_LOCATION=/mnt/appdata/immich/postgres
TZ=<TZ> # e.g. Etc/UTC
DB_PASSWORD=<YOUR_PASSWORD> # A-Za-z0-9 only
DB_USERNAME=postgres
DB_DATABASE_NAME=immich
The tags are literal in the compose file rather than ${IMMICH_VERSION}: Renovate can only bump what it can see, and
a version that lives in a host-only .env drifts silently (that's exactly how my ML container once ended up on a
different version than the server).
Machine learning (separate host)¶
name: immich-ml
services:
immich-machine-learning:
container_name: immich_machine_learning
image: ghcr.io/immich-app/immich-machine-learning:v3.3.5-openvino
extends:
file: hwaccel.ml.yml
service: openvino # Intel iGPU; cuda / rocm / armnn / rknn for other hardware
volumes:
- /mnt/appdata/immich/machinelearning/model-cache:/cache
ports:
- '3003:3003'
restart: always
healthcheck:
disable: false
No .env is needed on this side. The model cache is a bind mount on the SSD, so models survive a container recreate
and don't re-download.
The ML container has no authentication
Per Immich's own docs it has "no security measures whatsoever". Only the Immich server should be able to reach
port 3003: firewall it to the NAS's address, or bind it to an interface only the server can reach. Check that it
answers from the server: curl http://<ML_HOST_IP>:3003/ping should return pong.
IMMICH_MACHINE_LEARNING_URL sets the default URL; you can also add URLs under Administration > Settings > Machine
Learning. If you list several, Immich tries them in order, so you could keep a local ML container as a fallback.
I don't: with only the remote URL, Smart Search and Face Detection jobs fail while the ML host is down and need a
re-run ("Missing" on the Job Status page) afterwards.
Initial Setup¶
Start the ML stack first, then the server stack (docker compose up -d in each directory).
Navigate to http://<YOUR_SERVER_IP>:2283 to see the "Getting Started" wizard.
- Click Getting Started.
- Create Admin Account: Enter your email, name, and password.
- Log in: You will be taken to the main timeline.
Hardware Acceleration¶
Video Transcoding (Intel QuickSync)¶
Why use it: When you view a 4K video on a phone over a slow connection, Immich must transcode (shrink) that video in real-time. Doing this on the CPU will pin your processor to 100% usage and create stuttering. QuickSync offloads this to the iGPU.
How to configure:
The hwaccel.transcoding.yml in the compose file handles the backend permissions, but you must enable it in the UI.
- Go to Administration (top right) -> Settings -> Video Transcoding.
- Find Hardware Acceleration.
- Select Quick Sync from the dropdown.
- Click Save.
Machine Learning (OpenVINO)¶
Why use it: Immich scans every photo for faces and objects. On a raw CPU, scanning 1,000 photos can take hours. OpenVINO optimizes these mathematical operations for Intel CPUs, significantly increasing indexing speed.
How to configure:
There is no UI toggle for this. It is controlled entirely by the image tag (-openvino) and the extends block in the compose file.
Verification: Run the following on the ML host to check if it loaded correctly:
- Success: You will see a line stating
Loaded execution provider: OpenVINO. - Failure: If you only see
CPUExecutionProvider, check that your image tag ends in-openvino.
Database & Storage¶
PostgreSQL with pgvector¶
Immich requires a specific version of Postgres equipped with pgvector. This extension allows the database to store "vector embeddings"—mathematical representations of images. This enables smart searches (e.g., searching for "red truck" finds trucks without manual tags).
Performance Tip
Unlike Nextcloud, you rarely touch the DB config manually. However, ensure DB_DATA_LOCATION in your .env points to fast storage (SSD/NVMe). Vector searches on HDDs are noticeably slower.
Storage Template¶
By default, Immich uploads files into a generic folder structure. If you ever want to access your files directly on the disk (outside of Immich), this is difficult to navigate.
The Fix: Configure this before uploading your library.
- Go to Administration -> Settings -> Storage Template.
- Enabled: Toggle ON.
- Template: Select or customize your structure.
- Recommendation:
{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}} -
Result:
/2024/2024-12-25/IMG_1234.jpg -
Click Save.
Post-Install Monitoring¶
GPU Utilization¶
Since you are using hardware acceleration, verify the load is hitting the GPU and not the CPU.
The Tool: intel_gpu_top
-
Install on Host (Debian; run it on whichever host you're checking: NAS for transcoding, ML host for OpenVINO):
-
Run Monitor:
-
Test: Open Immich and scroll rapidly through the timeline (triggers thumbnail generation) or play a large video. You should see the
VideoorRenderbars spike.