Immich ha raggiunto una maturità che il self-hosting fotografico non aveva mai conosciuto. La versione 2.7 ha consolidato un progetto open source nato nel 2022 e che oggi conta oltre 100K stelle su GitHub, una community attivissima e una sostanziale parità di funzioni con Google Photos. Il punto interessante per chi fa self-hosting in modo serio è capire come spremere davvero la parte AI dello stack, perché è lì che si gioca la partita contro i giganti del cloud.
Sotto il cofano, Immich integra un servizio di machine learning separato che gestisce face recognition, object detection e ricerca semantica via CLIP. Tutto può girare in CPU, ma su una libreria importante diventa un collo di bottiglia inaccettabile. Per indicizzare 50.000 foto su un mini pc con Intel N100 senza accelerazione hardware si parla di giorni, non di ore.
La buona notizia è che il progetto supporta cinque backend di accelerazione (CUDA, OpenVINO, ROCm, RKNN, ARM NN), permette di sostituire i modelli predefiniti con varianti multilingua più capaci, e include la modalità External Library che cambia il discorso della migrazione da soluzioni preesistenti. Vediamo come configurarlo in modo da avere una piattaforma fotografica privata che regge il confronto con il cloud anche su volumi importanti.
Immich: l’architettura che serve
Le guide standard parlano di “4 GB di RAM e un Docker host” come requisito di base. Per chi intende sfruttare la parte ML, è una stima pericolosamente ottimistica. Lo stack si compone di sei container: l’unità di machine-learning, PostgreSQL con estensione VectorChord, Redis (oggi Valkey è il default) e, opzionalmente, un reverse proxy. Tale modulo, con i modelli caricati in memoria, richiede tra 2 e 6 GB di RAM a seconda della scelta dei modelli, e PostgreSQL con i vettori di embedding può arrivare a 1-2 GB senza fatica.
Per librerie sotto le 20.000 foto, 8 GB di RAM e una CPU con quattro core moderni sono adeguati, ma oltre queste scale conviene comunque pianificare l’accelerazione GPU. Face detection e CLIP embedding sono operazioni intrinsecamente parallele, e una iGPU Intel Arc integrata in un Core di dodicesima generazione è quattro o cinque volte più veloce della CPU dello stesso processore. Se invece hai un Raspberry Pi 5 o un mini-PC con NPU Rockchip, il backend RKNN sblocca prestazioni inaspettate.
Lo storage va pianificato in due tier separati. Il database PostgreSQL deve obbligatoriamente vivere su disco locale (mai su NFS o SMB, è una causa nota di corruzione), mentre la libreria foto può risiedere su un array ZFS o un NAS montato via fstab.
Scegliere il backend di accelerazione giusto
Il container immich-machine-learning supporta cinque suffix di immagine, ciascuno con caratteristiche diverse. La scelta non è banale e dipende strettamente dall’hardware disponibile.
Per chi possiede una GPU NVIDIA dedicata, CUDA resta la strada più diretta. Servono compute capability ≥ 5.2 (anche una vecchia GTX 1050 va bene), driver ≥ 545 e l’NVIDIA Container Toolkit installato sull’host. Sulle iGPU Intel dalla settima generazione in poi, OpenVINO è la scelta vincente, soprattutto sulle Iris Xe e sulle Arc integrate, dove le prestazioni si avvicinano a GPU discrete entry-level.
Per AMD c’è ROCm, ma l’immagine pesa circa 35 GB e il supporto è meno solido, quindi vale la pena solo se la GPU figura ufficialmente nella lista compatibile.
I due backend più interessanti sono RKNN per le NPU Rockchip (Orange Pi 5, Radxa Rock e simili) e ARM NN per i SoC ARM64 con Mali. Su un Orange Pi 5 con NPU da 6 TOPS, indicizzare 10.000 foto passa da circa 12 ore in CPU a meno di 90 minuti.
La configurazione è più semplice di quanto sembri. Bisogna aggiungere il suffix all’immagine ML e dichiarare i device GPU nella sezione deploy. Ecco un docker-compose.yml completo per un setup con accelerazione CUDA, pronto da incollare in un nuovo host con NVIDIA Container Toolkit già installato.
name: immich
services:
immich-server:
container_name: immich_server
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
volumes:
- ${UPLOAD_LOCATION}:/data
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
ports:
- 2283:2283
depends_on:
- redis
- database
restart: always
healthcheck:
disable: false
immich-machine-learning:
container_name: immich_machine_learning
# Nota il suffix -cuda al posto della versione standard
image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}-cuda
volumes:
- model-cache:/cache
env_file:
- .env
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities:
- gpu
restart: always
healthcheck:
disable: false
redis:
container_name: immich_redis
image: docker.io/valkey/valkey:8-bookworm
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
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_DB: ${DB_DATABASE_NAME}
POSTGRES_INITDB_ARGS: '--data-checksums'
volumes:
- ${DB_DATA_LOCATION}:/var/lib/postgresql/data
restart: always
volumes:
model-cache:Il file .env accanto al compose contiene le variabili UPLOAD_LOCATION, DB_DATA_LOCATION, DB_PASSWORD, DB_USERNAME e DB_DATABASE_NAME.
Per setup multi-GPU si aggiungono MACHINE_LEARNING_DEVICE_IDS=0,1 e MACHINE_LEARNING_WORKERS=2. La verifica del corretto funzionamento è semplice. Dopo il primo docker compose up -d, nei log del container ML deve comparire la riga Available ORT providers con il provider scelto, e lanciare nvtop sull’host mostra la GPU che lavora durante l’indicizzazione.
Immich: modelli CLIP per la ricerca semantica
La feature di punta di Immich resta la ricerca semantica via CLIP, quella che ti permette di scrivere “cane in spiaggia al tramonto” e ottenere risultati pertinenti senza alcun tag manuale. Il modello predefinito è ViT-B-32__openai, ottimo come baseline ma con un limite, ovvero parla solo inglese.
La soluzione passa dai modelli multilingua, sostituibili dall’interfaccia admin sotto Settings → Machine Learning. XLM-Roberta-Large-Vit-B-16Plus è il workhorse classico, supporta oltre cento lingue, pesa circa 2 GB e gira decentemente anche su CPU.
Per hardware più solido, nllb-clip-large-siglip__v1 è oggi la nuova frontiera, con punteggi superiori nei benchmark cross-lingua ma con un fabbisogno di 4 GB tra VRAM e RAM dedicata.
Cambiare modello richiede di rilanciare lo “Smart Search Job” sull’intera libreria, perché gli embedding salvati in database sono vincolati al modello che li ha generati. Su 50.000 foto serve pazienza, ma la differenza in pertinenza dei risultati è notevole.

A questo si aggiunge il riconoscimento OCR tramite PP-OCRv5, che indicizza i testi presenti nelle foto, tra cui cartelli, slide, screenshot di documenti e appunti scritti a mano. Combinato con CLIP, trasforma la libreria in un archivio interrogabile in linguaggio naturale a un livello che Google Photos non offre.
External Library e migrazione intelligente da Google Photos
Una caratteristica spesso sottovalutata di Immich è la modalità External Library. A differenza dell’upload classico, dove le foto vengono copiate nello storage gestito da Immich, qui si montano directory esistenti in sola lettura e si indicizzano senza spostarle. Per chi ha già una struttura organizzata su NAS, magari pianificata in cartelle per anno o per evento, è la soluzione naturale. Tutti i metadati e le funzioni ML lavorano normalmente, ma i file restano dove sono.

La configurazione passa per due punti distinti. Prima di tutto bisogna montare la directory esterna nel container immich-server come volume in sola lettura, dichiarandolo nel docker-compose.yml.
immich-server:
volumes:
- ${UPLOAD_LOCATION}:/data
- /mnt/nas/foto:/mnt/photos:ro
- /mnt/nas/scansioni:/mnt/scans:ro
- /etc/localtime:/etc/localtime:roSuccessivamente, dall’interfaccia admin (Administration → External Libraries), si aggiunge il percorso interno al container (/mnt/photos, non quello dell’host) e si lancia la prima scansione.
Da quel momento Immich rileva automaticamente i nuovi file aggiunti via Syncthing, rsync o qualsiasi altro meccanismo. È possibile escludere pattern specifici con sintassi glob standard, per esempio **/.thumbnails/** per ignorare le thumbnail di altri sistemi, oppure **/*.{cr2,nef,arw} se preferisci indicizzare solo i JPEG e tenere i RAW fuori dalla pipeline ML.
Un dettaglio che spesso confonde è che le External Library non supportano l’editing non distruttivo introdotto in 2.5, perché Immich non vuole toccare file che non possiede. Per chi usa Lightroom o darktable accanto a Immich, questo è un vantaggio invece di un limite, dato che le modifiche restano nel software di editing originale.
Per chi viene da Google Photos lo strumento di riferimento è ormai immich-go, un client CLI scritto in Go che parsa correttamente gli archivi Google Takeout, incluse le date salvate nei file JSON sidecar che Google esporta separatamente. Il flusso completo, dal download alla validazione, si articola in pochi comandi.
# Scarica l'ultima release (Linux x86_64)
wget https://github.com/simulot/immich-go/releases/latest/download/immich-go_Linux_x86_64.tar.gz
tar -xzf immich-go_Linux_x86_64.tar.gz
# Esegui upload da Google Takeout
./immich-go upload from-google-photos \
--server https://immich.miodominio.tld \
--api-key abc123xxxxxxxxxxxxx \
--create-albums \
--discard-archived \
--upload-when-missing-date local-time \
./takeout-20260301T120000Z-001.zip \
./takeout-20260301T120000Z-002.zip
Il flag --create-albums ricostruisce gli album originali, mentre --discard-archived evita di reimportare le foto che avevi spostato in archivio su Google. L’opzione --upload-when-missing-date local-time gestisce il caso (frequente) di foto a cui Google Takeout non associa una data EXIF valida, con fallback al fuso orario locale invece di rifiutarle.
Tra i problemi più comuni con cui ci si scontra c’è la deduplicazione. Google Takeout esporta spesso lo stesso file in più ZIP quando la libreria è cresciuta nel tempo. immich-go calcola un hash di ogni file prima dell’upload e salta i duplicati, ma se la libreria di destinazione è già parzialmente popolata conviene aggiungere --api-trace la prima volta per verificare cosa sta succedendo.
Il consiglio è di iniziare con un mese di foto per validare il setup, e procedere incrementalmente. Poi meglio spezzare il Takeout in batch da 50 GB, lanciare l’upload, lasciare che il container ML smaltisca face detection e CLIP embedding, e solo dopo proseguire con il batch successivo. La macchina lavora meglio, e in caso di problemi ricominci da un punto noto.
Immich: compromessi da accettare
Immich occupa una posizione che pochi progetti self-hosted hanno raggiunto. Non è più “un’alternativa accettabile a Google Photos” ma un prodotto che, configurato bene, offre funzioni che Google Photos non ha. La ricerca con OCR integrato, i modelli CLIP multilingua sostituibili a piacere, l’External Library che rispetta le strutture esistenti su disco sono tutte caratteristiche che nel mondo cloud sono assenti oppure gabbiate dietro piani enterprise.
Restano alcuni problemi da non minimizzare. La curva di apprendimento per chi non ha mai messo mano a un docker-compose è ripida, e la documentazione ufficiale, pur ottima, presuppone che si sappia cos’è una compute capability NVIDIA o un device ID di un container Docker.
La gestione dei backup del database è interamente a carico di chi gestisce l’istanza, e va presa sul serio. Perdere PostgreSQL significa rigenerare gli embedding di tutta la libreria, non perdere le foto in sé. Il team sta lavorando a un servizio di backup gestito con cifratura end-to-end, ma per il momento si va di pg_dump e cron.
Il consiglio è di non rimandare due decisioni al momento dell’installazione. Scegli subito un modello CLIP multilingua se la libreria è in italiano, e configura l’accelerazione hardware dal primo giorno. Migrare in seguito è doloroso, perché significa ricalcolare tutti gli embedding. Se tutto il processo è fatto bene, hai un Google Photos che è davvero tuo, e che in alcune cose lavora meglio dell’originale.













