SSD Nodes Learn Hosting plans →
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-08-24

Come fare backup e ripristino di Immich su VPS

Scopri cosa includere nel backup di Immich, perché copiare la directory dati di Postgres non basta e quale errore produce una timeline vuota.

Cosa deve contenere un backup di Immich

Un backup di Immich comprende tre elementi acquisiti nello stesso momento. Gli originali in UPLOAD_LOCATION. Un dump SQL del database Postgres. I file .env e docker-compose.yml che descrivono lo stack. Il ripristino consiste nel reinserire il dump in un database nuovo mentre il server Immich è fermo, quindi nell'avviare il resto dello stack solo dopo questa operazione. Se si esegue la procedura nell'ordine sbagliato, Immich può avviarsi correttamente ma mostrare una timeline vuota sopra un disco pieno.

La separazione è importante perché Immich conserva il proprio stato in due posizioni che non comunicano tra loro. Postgres contiene ogni album, ogni cluster di volti, ogni link condiviso, ogni account utente e chiave API, oltre al percorso memorizzato di ogni asset. Il filesystem contiene i pixel. Se si ripristinano i file senza il database, Immich non mostra alcun contenuto. Se si ripristina il database senza i file, ogni asset restituisce un'immagine non disponibile.

I comandi riportati qui si riferiscono a Immich v3.1.0, la release corrente all'inizio di agosto 2026. Il progetto rilascia rapidamente nuove versioni e la procedura di backup documentata è cambiata più volte. Verificare quindi la versione effettivamente in esecuzione prima di copiare qualsiasi elemento. Se lo stack non è ancora attivo, iniziare da la guida all'installazione di Immich e poi tornare qui.

Identifica la destinazione dei percorsi

Due variabili in .env determinano tutto il contenuto di questa pagina. UPLOAD_LOCATION è la directory principale in cui Immich scrive tutti i file multimediali. DB_DATA_LOCATION è la directory dei dati di Postgres.

Il file example.env predefinito imposta UPLOAD_LOCATION=./library. È un valore predefinito fuorviante, perché Immich crea quindi una directory chiamata library al suo interno. Gli originali finiscono in ./library/library. Imposta invece un percorso assoluto, così uno script di backup non dipenderà mai dalla directory da cui lo esegui.

UPLOAD_LOCATION=/srv/immich/data
DB_DATA_LOCATION=/srv/immich/postgres
DB_USERNAME=postgres
DB_DATABASE_NAME=immich
IMMICH_VERSION=v3.1.0

All'interno di UPLOAD_LOCATION, Immich crea diverse directory. Tre contengono dati che nessun job può ricostruire:

  • library: gli originali, organizzati secondo il template di archiviazione
  • upload: gli originali non ancora spostati nella struttura del template e i caricamenti in corso
  • profile: le immagini del profilo degli utenti

Se perdi library, la foto è persa. Immich non conserva in alcun altro punto una seconda copia dell'originale.

Perché copiare la directory dei dati di Postgres non è un backup

DB_DATA_LOCATION sembra un obiettivo semplice. È una directory, rsync la copia e la copia termina senza errori. Non è comunque un backup, per due motivi che possono causare errori.

Il primo è l'incoerenza della copia. Postgres scrive prima ogni modifica nel write-ahead log (WAL), quindi la applica ai file delle tabelle durante un checkpoint. In qualsiasi momento, quindi, i file sul disco possono trovarsi in uno stato intermedio. Una copia incrementale che richiede quattro minuti legge il primo file alle 02:00 e l'ultimo alle 02:04. Questi due file non appartengono alla stessa transazione. Quando avvii Postgres usando il risultato, il server rifiuta l'avvio con PANIC: could not locate a valid checkpoint record oppure si avvia e si arresta alla prima lettura di una pagina danneggiata con invalid page in block 1234 of relation base/16384/.... In nessuno dei due casi è possibile recuperare i dati da quella copia.

Il secondo motivo resta valido anche se arresti prima tutti i servizi. Una directory dei dati di Postgres è legata agli esatti binari che l'hanno scritta. Immich fissa l'immagine del database tramite digest, attualmente ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0. Si tratta di Postgres 14 con due estensioni per la ricerca vettoriale compilate al suo interno. Una directory dei dati scritta da quella build non può essere aperta con una versione major diversa di Postgres e non può essere aperta nemmeno con una build che contiene versioni diverse delle estensioni. L'host di ripristino deve quindi riprodurre esattamente l'immagine originale. Un dump SQL non ha questo vincolo: è testo e qualsiasi server compatibile può rielaborarlo.

pg_dump elimina alla radice il problema dell'incoerenza della copia. Legge l'intero database all'interno di una singola snapshot MVCC (multi-version concurrency control), quindi vede il database esattamente nello stato in cui si trovava in un istante preciso, mentre le altre scritture continuano. Per questo non è necessario arrestare Postgres per eseguire il dump.

Cosa si può escludere dal backup

Questi contenuti vengono rigenerati, quindi si possono omettere:

  • thumbs: immagini di anteprima e miniature
  • encoded-video: video transcodificati
  • DB_DATA_LOCATION: contenuti ricostruiti dal dump
  • il volume Docker model-cache: modelli di machine learning, scaricati di nuovo quando necessario

Ometterli è una scelta con un compromesso, non un vantaggio gratuito. La ricostruzione di miniature e transcodifiche per una libreria di grandi dimensioni può richiedere ore di CPU su un VPS di piccole dimensioni, mentre la timeline mostra segnaposto grigi per tutto il tempo. È possibile eseguire nuovamente queste operazioni da Administration > Jobs, impostando "Generate Thumbnails" e "Transcode Videos" in modo che vengano eseguite sugli asset mancanti. Se la destinazione del backup ha spazio sufficiente, includili ed evita l'attesa. Se sei vicino al limite dello spazio di archiviazione, omettili e pianifica la ricostruzione. Dimensionamento di una libreria Immich spiega quanto possono crescere queste directory rispetto agli originali.

È utile conoscere anche un'altra directory. UPLOAD_LOCATION/backups contiene i dump automatici del database creati da Immich, scritti ogni giorno alle 02:00 e conservati in un massimo di 14 copie; la configurazione si trova in Administration > Settings > Backup. Non occupano spazio aggiuntivo e sono realmente utili. Si trovano però sullo stesso disco della libreria che proteggono, quindi aiutano in caso di una migrazione non riuscita, ma non in caso di guasto del server. Crea comunque un dump autonomo, perché un dump avviato manualmente viene creato nello stesso momento dello snapshot dei file associato a quel dump.

Eseguire il dump del database

docker exec -t immich_postgres pg_dump --clean --if-exists \
  --dbname=immich --username=postgres \
  | gzip > /srv/immich/backup/immich.sql.gz

Sostituisci immich e postgres con i tuoi valori DB_DATABASE_NAME e DB_USERNAME, se li hai modificati. --clean --if-exists inserisce un DROP ... IF EXISTS prima di ogni CREATE, quindi il dump viene ripristinato in un database che contiene già oggetti, invece di interrompersi al primo oggetto.

Ora considera il dettaglio che compromette silenziosamente gli script di backup. Quel comando è una pipeline e la shell restituisce lo stato di uscita dell'ultimo comando della pipeline. Se pg_dump non riesce, a causa di una password errata o perché il container non è in esecuzione, gzip riceve un flusso vuoto, scrive un file gzip perfettamente valido e termina con codice 0. Lo script registra un esito positivo e ti ritrovi con un backup di 20 byte. Inserisci pipefail all'inizio di ogni script di backup:

#!/usr/bin/env bash
set -euo pipefail

Poi verifica il risultato invece di affidarti al codice di uscita:

ls -lh /srv/immich/backup/immich.sql.gz
gunzip -c /srv/immich/backup/immich.sql.gz | head -n 3

La prima riga di un dump valido è -- PostgreSQL database dump. Un file di poche centinaia di byte indica un dump non riuscito, indipendentemente da quanto riportato dallo script.

Registra la build che lo ha generato accanto al dump:

docker inspect --format '{{.Config.Image}}' immich_server > /srv/immich/backup/immich-version.txt

Non affidarti a .env per questo scopo. Il file predefinito imposta IMMICH_VERSION=v3, un tag mobile che segue ogni release 3.x, quindi non indica quale build ha effettivamente generato il dump. Specifica il tag esatto anche in .env.

Mettere in pausa il server, quindi creare uno snapshot con restic

I file in UPLOAD_LOCATION non sono immutabili mentre Immich è in esecuzione. Il server scrive i nuovi upload e il job del template di storage sposta i file tra le directory. Se uno strumento di backup legge un file durante la scrittura, salva quei byte come se fossero l’intero file, senza segnalare errori. Arrestare il container del server per tutta la durata dell’operazione:

docker stop immich_server

Lasciare immich_postgres in esecuzione, perché il dump ne ha bisogno. L’interfaccia web e l’app mobile restano offline finché il server non viene riavviato; su un’istanza domestica, alle 03:00, di solito non è un problema.

restic è adatto in questo caso perché deduplica e cifra i dati prima che lascino il server. Configurarlo in modo che utilizzi un repository che non si trova su questo server:

export RESTIC_REPOSITORY=sftp:backup@backup.example.com:/srv/restic/immich
export RESTIC_PASSWORD_FILE=/root/.restic-password
restic init

Lo storage a oggetti funziona nello stesso modo ed è la scelta migliore se si vuole mantenere la copia completamente fuori dal proprio hardware:

export RESTIC_REPOSITORY=s3:https://s3.example.com/immich-backup
export AWS_ACCESS_KEY_ID=your-access-key
export AWS_SECRET_ACCESS_KEY=your-secret-key
restic init

L’endpoint può essere un bucket MinIO gestito autonomamente su una seconda macchina oppure qualsiasi provider compatibile con S3. Un repository sullo stesso disco della libreria protegge da una cancellazione accidentale, ma da nient’altro.

Quindi creare lo snapshot, includendo esattamente ciò che serve:

restic backup \
  /srv/immich/backup/immich.sql.gz \
  /srv/immich/backup/immich-version.txt \
  /srv/immich/data/library \
  /srv/immich/data/upload \
  /srv/immich/data/profile \
  /srv/immich/.env \
  /srv/immich/docker-compose.yml
docker start immich_server

restic legge l’intero albero a ogni esecuzione, ma carica solo i blocchi che non ha già rilevato. Il primo snapshot trasferisce quindi l’intera libreria; ogni snapshot successivo trasferisce solo le nuove foto della giornata.

Conservazione e chiavi che devono essere archiviate altrove

restic forget --prune --keep-daily 7 --keep-weekly 5 --keep-monthly 12

forget rimuove gli snapshot dall'indice. --prune è la parte che elimina i dati ai quali quegli snapshot facevano da ultimo riferimento. Eseguite forget senza --prune e il costo dello storage non diminuirà mai.

I controlli della struttura sono rapidi, quindi eseguitene uno ogni settimana:

restic check

Questo verifica che i metadati del repository siano coerenti. Non legge i dati. Una volta al mese, rileggete un campione e confrontatelo con gli hash registrati:

restic check --read-data-subset=5%

Questo è l'unico controllo che rileva la corruzione silenziosa sul backend di storage, perché scarica blocchi reali e ne ricalcola i checksum. Un --read-data completo su una libreria di foto richiede il download dell'intero repository. Su uno storage a oggetti con tariffazione a consumo, il costo può essere significativo. Per questo, nella pratica si usa un sottoinsieme a rotazione.

Ora veniamo alla parte che spesso viene ignorata. La password di un repository restic non è recuperabile. Non esiste una procedura di reimpostazione e non è possibile aprire un ticket al supporto. Se l'unica copia si trova in /root/.restic-password sul server che state cercando di ripristinare, i backup sono dati cifrati inutilizzabili. Lo stesso vale per la chiave di accesso allo storage a oggetti e per DB_PASSWORD da .env. Conservateli tutti in un luogo che non dipenda dal funzionamento di questa macchina: su carta e in un cassetto, oppure in un password manager eseguito su hardware diverso. Se anche quel password manager è self-hosted, deve essere protetto nello stesso modo e il backup di Vaultwarden è un'attività separata.

Ripristinare Immich nell'ordine corretto

L'ordine di ripristino determina se un backup valido restituisce le timeline oppure un'installazione vuota. Sul nuovo host, segui questa sequenza.

Ripristina prima la configurazione. Indica quale versione eseguire e a quali percorsi fanno riferimento i mount.

restic restore latest --target /restore \
  --include /srv/immich/.env \
  --include /srv/immich/docker-compose.yml \
  --include /srv/immich/backup

Blocca la versione prima di avviare qualsiasi componente. Leggi immich-version.txt, imposta IMMICH_VERSION in .env su quel tag esatto e, per ora, non usare la release più recente. Immich non supporta il downgrade, neppure tra patch release. Se un server più recente si avvia su un dump precedente ed esegue le migrazioni, non è possibile tornare indietro.

Ripristina i file multimediali.

restic restore latest --target /restore --include /srv/immich/data

Sposta quindi library, upload e profile in modo che si trovino direttamente all'interno del percorso indicato da UPLOAD_LOCATION su questo host. Il percorso dell'host può cambiare, perché il file compose associa quella directory a un percorso fisso all'interno del container. La struttura interna non può cambiare.

Avvia il database separatamente. Lascia DB_DATA_LOCATION vuoto, così Postgres inizializza un cluster nuovo.

cd /srv/immich
docker compose pull
docker compose create
docker start immich_postgres
docker exec immich_postgres pg_isready --username=postgres

pg_isready stampa accepting connections al termine della configurazione iniziale, che richiede alcuni secondi. docker compose create crea tutti i container senza avviarli. Questo è lo scopo del passaggio: il server Immich non deve ancora essere in esecuzione. Un server avviato su un database vuoto esegue le migrazioni, crea uno schema nuovo e chiede di creare un nuovo account amministratore. A quel punto ripristineresti il dump sotto un'applicazione già in esecuzione.

Ripristina il dump.

gunzip --stdout /restore/srv/immich/backup/immich.sql.gz \
  | sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" \
  | docker exec -i immich_postgres psql --dbname=immich --username=postgres \
      --single-transaction --set ON_ERROR_STOP=on

Due elementi svolgono una funzione importante. sed è necessario perché pg_dump inserisce un search_path vuoto nel proprio output come misura di sicurezza. In questo modo, i nomi non qualificati presenti nel dump non possono risolversi in uno schema imprevisto. I tipi per la ricerca vettoriale di Immich si trovano in public. Con un search path vuoto, il ripristino raggiunge la prima colonna dichiarata con un tipo vector e psql si interrompe con ERROR: type "vector" does not exist. Aggiungere nuovamente public al path risolve il problema.

--single-transaction --set ON_ERROR_STOP=on esegue l'intero ripristino in un'unica transazione, che viene annullata al primo errore. Ottieni quindi un database completo oppure un database invariato. Senza questa opzione, un errore a metà ripristino lascia un database che si avvia e accetta il login, ma al quale manca un numero imprecisato di album. Potresti accorgertene solo dopo settimane.

Ora avvia tutti i componenti.

docker compose up -d
docker compose ps
docker logs -f immich_server

Attendi una riga di avvio simile a Immich Server is listening on, quindi apri la porta 2283 ed esegui il login con le credenziali precedenti, perché gli account utente sono stati ripristinati insieme al dump. Se la pagina di login propone invece di creare il primo account amministratore, il database non è stato ripristinato. Arresta il servizio e controlla nuovamente l'output di psql.

Una precisazione sulle istruzioni ufficiali per il ripristino, che iniziano con docker compose down -v. -v rimuove i volumi denominati. Nel file compose predefinito, UPLOAD_LOCATION e DB_DATA_LOCATION sono bind mount, quindi il comando non li rimuove. Se hai modificato uno dei due trasformandolo in un volume denominato, quel comando elimina le fotografie. Leggi il file compose prima di eseguirlo.

Perché la timeline è vuota dopo un ripristino

La timeline viene ricavata dalle righe del database. Immich non esegue la scansione di upload/ all'avvio per riscoprire le foto, perché un file senza una riga non ha proprietario, data o album. Pertanto, il ripristino incompleto più comune consiste nel ripristinare i file ma non il database. Immich si avvia, crea uno schema vuoto e fornisce un'istanza funzionante che non contiene nulla, mentre il disco è pieno di foto. Non è stato perso nulla. Tuttavia, non è visibile nulla. La correzione consiste nel riprodurre il dump con il server arrestato, esattamente come indicato sopra.

La seconda variante è meno evidente. Il database viene ripristinato, la timeline si riempie di elementi, ma ogni risorsa non viene aperta. Questo significa che le righe puntano a file che il container non riesce a vedere, in genere perché library, upload e profile si trovano a un livello di directory di troppo dopo un restic restore --target /restore che nessuno ha spostato nella posizione corretta. Controlla dall'interno del container invece di procedere per tentativi:

docker exec immich_server ls /data

Il file compose fornito di serie monta UPLOAD_LOCATION in /data, quindi l'elenco dovrebbe mostrare library, upload e profile. Se mostra una directory vuota o una cartella srv estranea, il bind mount punta al livello sbagliato e le righe sono corrette.

Corrispondenza della versione tra backup e ripristino

Immich viene rilasciato frequentemente e lo schema cambia insieme al software. Un dump contiene quindi lo schema del server che lo ha creato.

Il ripristino di un dump precedente su un server più recente in genere funziona, perché il server applica all'avvio le migrazioni in sospeso e aggiorna progressivamente lo schema. Questo percorso viene verificato nella sequenza delle release. I problemi si verificano quando si saltano diverse versioni principali in un solo passaggio. Il progetto concentra le modifiche incompatibili nelle versioni principali e le documenta nel changelog.

Il ripristino di un dump più recente su un server precedente non funziona. Il dump contiene tabelle e colonne che il codice più vecchio non riconosce. Immich dichiara inoltre che il downgrade non è supportato nemmeno tra versioni patch. Non esiste un comando di rollback da utilizzare.

Il ripristino sicuro è quindi semplice. Esegui la versione esatta che ha creato il dump, ripristinalo, accedi e verifica che la timeline sia completa. Esegui l'upgrade solo dopo questi controlli. Aggiorna una release alla volta, incrementando IMMICH_VERSION ed eseguendo docker compose pull && docker compose up -d dopo ogni aggiornamento. Conservare i dump di una settimana è utile anche in questo caso: se l'ultimo è stato creato durante un upgrade non riuscito, quello del giorno precedente è ancora disponibile nel repository.

Verifica il backup ogni mese

Un backup che non hai mai ripristinato è solo un’ipotesi. Una volta al mese, ripristinalo in un’istanza temporanea e controlla una foto. La verifica richiede circa venti minuti ed è l’unico passaggio che trasforma il resto di questa pagina in un piano di ripristino.

restic snapshots
restic stats latest

snapshots dovrebbe elencare l’esecuzione della scorsa notte. stats latest dovrebbe riportare una dimensione vicina a quella della tua libreria, non pochi megabyte.

Ripristina il backup in una directory temporanea, preferibilmente su un host di riserva:

restic restore latest --target /tmp/immich-drill

Copia docker-compose.yml e .env fuori dall’insieme ripristinato, quindi modifica tre elementi nella copia. Imposta UPLOAD_LOCATION e DB_DATA_LOCATION su directory sotto /tmp/immich-drill. Pubblica la porta web su un’altra porta, 12283:2283 invece di 2283:2283. Elimina le righe container_name:, perché il file compose predefinito codifica nomi come immich_server: un secondo stack sullo stesso host entrerebbe in conflitto con il primo e Docker rifiuterebbe di crearlo.

Esegui la procedura di ripristino descritta sopra: solo il database, riproduci il dump, quindi docker compose up -d. Ora esegui i quattro controlli che dimostrano che il ripristino è riuscito.

  1. Accedi con la password che usavi prima della verifica. Se gli account funzionano, il dump è stato ripristinato.
  2. Apri la timeline e scorri fino al mese più remoto. Se sono presenti risorse sull’intero intervallo di date, significa che sono state ripristinate tutte le righe, non solo quelle recenti.
  3. Apri una foto a dimensione originale e scarica il file originale.
  4. Confrontalo con lo stesso file nella libreria in produzione usando sha256sum. Se gli hash corrispondono, i byte hanno superato correttamente il ciclo di ripristino tramite restic.

Al termine, rimuovi l’ambiente della verifica con docker compose down -v nella directory usata per la verifica ed elimina /tmp/immich-drill. Annota la data in un punto visibile, perché il valore di questa procedura dipende interamente dal ripeterla il mese prossimo. Se stai ancora decidendo quale photo server adottare, il confronto tra PhotoPrism e Immich spiega in che modo i due prodotti differiscono proprio su questo aspetto.

FAQ

Devo arrestare Immich per eseguire il backup?

Arresta immich_server e lascia immich_postgres in esecuzione. Il database non deve essere messo in pausa, perché pg_dump legge all'interno di uno snapshot MVCC e vede un unico istante coerente, indipendentemente dalle altre operazioni di scrittura. Il motivo per cui è necessario arrestare il server riguarda i file: il server scrive i nuovi caricamenti e il job del template di archiviazione sposta i file tra le directory, quindi uno strumento di backup può leggere un file mentre è ancora in scrittura e salvarne una copia troncata senza segnalare errori. Esegui docker stop immich_server prima dello snapshot e docker start immich_server dopo lo snapshot per eliminare questa condizione di gara.

Posso copiare la directory dei dati di Postgres invece di eseguire pg_dump?

No. Una copia progressiva di una directory dati attiva legge file diversi in istanti diversi. Il risultato non rappresenta quindi uno stato coerente e Postgres la rifiuta all'avvio con PANIC: could not locate a valid checkpoint record oppure si arresta in seguito a causa di una pagina danneggiata. Anche una copia eseguita a tutti i servizi arrestati dipende dalla build esatta del database: Immich blocca un'immagine Postgres 14 con versioni specifiche dell'estensione per la ricerca vettoriale e la directory non può essere aperta da una build diversa. Un dump SQL è testo semplice e può essere ripristinato su qualsiasi server compatibile.

Perché la timeline di Immich è vuota dopo un ripristino?

Perché la timeline viene costruita a partire dalle righe del database e hai ripristinato i file senza il database. Immich non esegue mai la scansione di upload/ per riscoprire le foto, quindi i file senza righe corrispondenti restano invisibili. Le foto non sono state modificate. Arresta il server, ripristina il dump in un'istanza Postgres appena inizializzata, quindi avvia lo stack. Se invece la timeline è completa ma ogni foto non si apre, il problema è opposto: library, upload e profile non si trovano direttamente nella directory associata al container. Verificalo con docker exec immich_server ls /data.

Quali directory di Immich posso escludere dal backup?

thumbs e encoded-video vengono rigenerate dagli originali, mentre DB_DATA_LOCATION viene ricostruita dal dump. Nessuna di queste directory deve quindi essere inclusa nel set di backup. Escluderle sposta il tempo necessario dopo il ripristino invece di richiedere spazio prima del ripristino, perché la ricostruzione delle anteprime e delle transcodifiche per una libreria di grandi dimensioni richiede ore di CPU ed è eseguita da Administration > Jobs sugli asset mancanti. Non puoi mai escludere library, upload e profile, che contengono l'unica copia di ogni originale.

Posso ripristinare un dump di Immich in una versione più recente?

In genere sì, perché il server applica all'avvio le migrazioni in sospeso e aggiorna progressivamente lo schema. Il contrario non funziona: Immich non supporta il downgrade, neppure tra release di patch, quindi un dump proveniente da una release più recente non può essere caricato in un server più vecchio. Esegui il ripristino con IMMICH_VERSION bloccato sulla release che ha scritto il dump, verifica che la timeline sia completa e aggiorna solo dopo questa verifica. Registra la versione accanto a ogni dump con docker inspect --format '{{.Config.Image}}' immich_server, perché il valore predefinito IMMICH_VERSION=v3 è un tag mobile che non fornisce alcuna informazione utile.