Como fazer backup e restaurar o Immich em um VPS
Veja o que salvar no Immich v3.1.0, por que copiar o diretório do Postgres falha e como evitar restaurar a base antes dos arquivos e perder a linha do tempo.
O que um backup do Immich deve conter
Um backup do Immich consiste em três elementos capturados ao mesmo tempo. Os originais em UPLOAD_LOCATION. Um dump SQL da base de dados Postgres. Os ficheiros .env e docker-compose.yml que descrevem a stack. A restauração consiste em importar esse dump para uma base de dados nova com o servidor Immich parado e iniciar o restante da stack só depois disso. Se inverter a ordem, ficará com um Immich funcional, mas com uma linha do tempo vazia sobre um disco cheio.
A separação é importante porque o Immich mantém o estado em dois locais que não têm conhecimento um do outro. O Postgres guarda todos os álbuns, todos os grupos de rostos, todas as ligações partilhadas, todas as contas de utilizador e chaves de API, além do caminho armazenado de cada ativo. O sistema de ficheiros guarda os píxeis. Se restaurar os ficheiros sem a base de dados, o Immich não mostra nada. Se restaurar a base de dados sem os ficheiros, cada ativo abre como uma imagem danificada.
Os comandos apresentados foram escritos para o Immich v3.1.0, a versão atual no início de agosto de 2026. O projeto lança versões rapidamente e o procedimento de backup documentado mudou mais de uma vez. Por isso, confirme a versão que está efetivamente a executar antes de copiar qualquer elemento. Se a stack ainda não estiver ativa, comece pelo guia de instalação do Immich e volte aqui.
Saiba para onde apontam os seus caminhos
Duas variáveis em .env determinam tudo nesta página. UPLOAD_LOCATION é o diretório-pai onde o Immich grava todos os ficheiros multimédia. DB_DATA_LOCATION é o diretório de dados do Postgres.
O example.env predefinido define UPLOAD_LOCATION=./library, que é um valor predefinido confuso, porque o Immich cria depois uma pasta chamada library dentro desse diretório. Os originais ficam em ./library/library. Defina um caminho absoluto, para que um script de backup nunca dependa do diretório a partir do qual foi executado.
UPLOAD_LOCATION=/srv/immich/data
DB_DATA_LOCATION=/srv/immich/postgres
DB_USERNAME=postgres
DB_DATABASE_NAME=immich
IMMICH_VERSION=v3.1.0Dentro de UPLOAD_LOCATION, o Immich cria várias pastas. Três delas contêm dados que nenhum processo consegue reconstruir:
library: os originais, organizados de acordo com o seu modelo de armazenamentoupload: originais que ainda não foram movidos para a estrutura do modelo, além de uploads em cursoprofile: fotografias de perfil dos utilizadores
Se perder library, a fotografia desaparece. O Immich não mantém uma segunda cópia de nenhum original.
Por que copiar o diretório de dados do Postgres não é um backup
DB_DATA_LOCATION parece um alvo fácil. É um diretório, rsync vai copiá-lo e a cópia termina sem erros. Ainda assim, isto não é um backup, por dois motivos que podem causar falhas.
O primeiro é a inconsistência da cópia. O Postgres grava primeiro cada alteração no write-ahead log (WAL) e só depois a aplica aos ficheiros das tabelas, durante um checkpoint. Por isso, a qualquer momento, os ficheiros no disco estão num estado intermédio. Uma cópia sequencial que demora quatro minutos pode ler o primeiro ficheiro às 02:00 e o último às 02:04. Esses dois ficheiros não pertencem à mesma transação. Quando inicia o Postgres com o resultado, ele recusa arrancar com PANIC: could not locate a valid checkpoint record ou arranca e termina ao ler pela primeira vez uma página danificada, com invalid page in block 1234 of relation base/16384/.... Nenhuma das situações pode ser recuperada a partir dessa cópia.
O segundo motivo mantém-se mesmo que pare primeiro todos os serviços. Um diretório de dados do Postgres está associado aos binários exatos que o escreveram. O Immich fixa a imagem da base de dados por digest, atualmente ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0. Trata-se do Postgres 14 com duas extensões de pesquisa vetorial compiladas. Um diretório de dados escrito por essa compilação não abre noutra versão major do Postgres e também não abre numa compilação com versões diferentes das extensões. O servidor de recuperação tem de reproduzir exatamente a imagem. Um dump SQL não tem essa limitação: é texto e qualquer servidor compatível pode reproduzi-lo.
pg_dump elimina diretamente o problema da inconsistência da cópia. Lê toda a base de dados dentro de um único snapshot MVCC (multi-version concurrency control), pelo que vê a base de dados exatamente como estava num instante, enquanto as outras gravações continuam. Por isso, não é necessário parar o Postgres para criar o dump.
O que pode ficar fora do backup
Estes dados são recriados automaticamente, por isso podem ser excluídos:
thumbs: imagens de pré-visualização e miniaturasencoded-video: vídeos transcodificadosDB_DATA_LOCATION: recriados a partir do dump- o volume Docker
model-cache: modelos de machine learning, descarregados novamente quando necessário
Excluí-los é uma decisão com custos, não uma vantagem sem contrapartidas. Recriar miniaturas e transcodificações para uma biblioteca grande pode consumir horas de CPU num VPS pequeno, e a linha do tempo mostra marcadores cinzentos durante todo esse período. Pode executar novamente estas tarefas em Administration > Jobs, definindo "Generate Thumbnails" e "Transcode Videos" para serem executadas nos recursos em falta. Se o destino do backup tiver espaço, inclua-os e evite a espera. Se estiver perto do limite de armazenamento, exclua-os e planeie a recriação. Dimensionar uma biblioteca Immich explica quanto estas pastas crescem em relação aos originais.
Vale a pena conhecer mais uma pasta. UPLOAD_LOCATION/backups contém os dumps automáticos da base de dados do próprio Immich, gravados diariamente às 02:00, mantendo os últimos 14. Esta opção pode ser configurada em Administration > Settings > Backup. Estes dumps não consomem recursos adicionais relevantes e são realmente úteis. No entanto, ficam no mesmo disco que a biblioteca que protegem. Por isso, ajudam em caso de uma migração com problemas, mas não em caso de falha do servidor. Faça também o seu próprio dump, porque um dump iniciado por si é gravado no mesmo momento que o snapshot dos ficheiros correspondente.
Fazer o dump da base de dados
docker exec -t immich_postgres pg_dump --clean --if-exists \
--dbname=immich --username=postgres \
| gzip > /srv/immich/backup/immich.sql.gzSubstitua immich e postgres por DB_DATABASE_NAME e DB_USERNAME se os tiver alterado. --clean --if-exists coloca um DROP ... IF EXISTS antes de cada CREATE, para que o dump seja restaurado numa base de dados que já contém objetos, em vez de parar no primeiro objeto existente.
Agora, o detalhe que estraga silenciosamente os scripts de backup. Esse comando é um pipeline, e uma shell comunica o código de saída do último comando de um pipeline. Se pg_dump falhar devido a uma palavra-passe incorreta ou porque um contentor não está em execução, gzip recebe um fluxo vazio, grava um ficheiro gzip perfeitamente válido e termina com o código 0. O seu script regista sucesso e fica com um backup de 20 bytes. Coloque pipefail no início de todos os scripts de backup:
#!/usr/bin/env bash
set -euo pipefailDepois, verifique o resultado em vez de confiar no código de saída:
ls -lh /srv/immich/backup/immich.sql.gz
gunzip -c /srv/immich/backup/immich.sql.gz | head -n 3A primeira linha de um dump válido é -- PostgreSQL database dump. Um ficheiro com algumas centenas de bytes corresponde a um dump falhado, independentemente do que o script indicar.
Registe qual foi a build que o criou, junto ao dump:
docker inspect --format '{{.Config.Image}}' immich_server > /srv/immich/backup/immich-version.txtNão dependa de .env para isso. O ficheiro fornecido define IMMICH_VERSION=v3, uma tag flutuante que acompanha todas as versões 3.x. Por isso, não indica qual foi a build que criou efetivamente o dump. Fixe também a tag exata em .env.
Pause o servidor e faça um snapshot com restic
Os ficheiros em UPLOAD_LOCATION não são imutáveis enquanto o Immich está em execução. O servidor grava novos uploads, e o job de modelo de armazenamento move ficheiros entre diretórios. Se uma ferramenta de backup ler um ficheiro a meio de uma gravação, armazena esses bytes como se fossem o ficheiro completo, sem que seja reportado qualquer erro. Pare o contentor do servidor durante a execução:
docker stop immich_serverDeixe immich_postgres em execução, porque o dump precisa dele. A interface web e a aplicação móvel ficam indisponíveis até iniciar novamente o servidor. Numa instância doméstica, às 03:00, isto costuma ser aceitável.
restic é adequado neste caso porque deduplica e cifra os dados antes de estes saírem do servidor. Aponte-o para um repositório que não esteja neste servidor:
export RESTIC_REPOSITORY=sftp:backup@backup.example.com:/srv/restic/immich
export RESTIC_PASSWORD_FILE=/root/.restic-password
restic initO armazenamento de objetos funciona da mesma forma e é a melhor opção se quiser manter a cópia totalmente fora do seu próprio 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 initEsse endpoint pode ser um bucket MinIO que gere num segundo computador ou qualquer fornecedor compatível com S3. Um repositório no mesmo disco da biblioteca protege contra uma eliminação acidental, mas não contra mais nada.
Depois, crie o snapshot, listando exatamente o que importa:
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_serverO restic lê toda a árvore em cada execução, mas só carrega blocos que ainda não encontrou. Assim, o primeiro snapshot transfere toda a biblioteca e cada snapshot seguinte transfere apenas as fotografias novas do dia.
Retenção e as chaves que devem ficar noutro local
restic forget --prune --keep-daily 7 --keep-weekly 5 --keep-monthly 12forget remove snapshots do índice. --prune é a parte que elimina os dados para os quais esses snapshots eram a última referência. Execute forget sem --prune e o custo do armazenamento nunca diminui.
As verificações de estrutura são rápidas, por isso execute uma vez por semana:
restic checkIsto verifica se os metadados do repositório são consistentes. Não lê os seus dados. Uma vez por mês, volte a ler uma amostra e compare-a com os hashes registados:
restic check --read-data-subset=5%Esta é a única verificação que deteta corrupção silenciosa no backend de armazenamento, porque descarrega blocos reais e recalcula os respetivos checksums. Um --read-data completo numa biblioteca de fotografias implica descarregar todo o repositório. Num armazenamento de objetos com tarifação por utilização, isso tem um custo real. Por isso, um subconjunto rotativo é a opção que as pessoas realmente usam.
Agora, a parte que muitas pessoas ignoram. A palavra-passe de um repositório restic não pode ser recuperada. Não existe reposição nem pedido de suporte. Se a única cópia estiver em /root/.restic-password no servidor que está a tentar restaurar, os seus backups são dados encriptados sem utilidade. O mesmo se aplica à chave de acesso ao armazenamento de objetos e a DB_PASSWORD de .env. Guarde todos esses dados num local que não dependa de esta máquina estar ativa: impressos e guardados numa gaveta, ou num gestor de palavras-passe executado noutro hardware. Se esse gestor também for self-hosted, precisa do mesmo tratamento, e fazer backup do Vaultwarden é uma tarefa independente.
Restaure o Immich pela ordem correta
A ordem da restauração é o que transforma bons backups em cronologias vazias. Siga esta sequência no novo host.
Recupere primeiro a configuração. Ela informa qual versão deve ser executada e para onde apontam os caminhos.
restic restore latest --target /restore \
--include /srv/immich/.env \
--include /srv/immich/docker-compose.yml \
--include /srv/immich/backupFixe a versão antes de iniciar qualquer coisa. Leia immich-version.txt, defina IMMICH_VERSION em .env com essa tag exata e deixe a versão mais recente de lado por enquanto. O Immich não suporta downgrades, nem mesmo entre versões de correção. Se um servidor mais recente iniciar usando um dump antigo e executar as migrações, não haverá como voltar atrás.
Restaure os ficheiros multimédia.
restic restore latest --target /restore --include /srv/immich/dataDepois, mova library, upload e profile para que fiquem diretamente dentro do caminho para o qual UPLOAD_LOCATION aponta neste host. O caminho no host pode mudar, porque o ficheiro compose associa esse diretório a um caminho fixo dentro do container. A estrutura interna não pode mudar.
Inicie apenas a base de dados. Deixe DB_DATA_LOCATION vazio para que o Postgres inicialize um cluster novo.
cd /srv/immich
docker compose pull
docker compose create
docker start immich_postgres
docker exec immich_postgres pg_isready --username=postgrespg_isready imprime accepting connections depois de concluir a configuração inicial, o que demora alguns segundos. docker compose create cria todos os containers sem os iniciar. Esse é o objetivo desta etapa: o servidor Immich ainda não pode ser executado. Um servidor que inicia com uma base de dados vazia executa as migrações, cria um esquema novo e pede a criação de uma conta de administrador. Nesse momento, estará a restaurar um dump por baixo de uma aplicação em execução.
Restaure o 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=onDuas partes desse comando fazem um trabalho importante. sed existe porque pg_dump escreve um search_path vazio na saída como medida de segurança. Assim, os nomes não qualificados no dump não podem ser resolvidos para um esquema inesperado. Os tipos de pesquisa vetorial do Immich estão em public. Com um caminho de pesquisa vazio, a restauração chega à primeira coluna declarada com um tipo vetorial e o psql termina com ERROR: type "vector" does not exist. Colocar public novamente no caminho resolve o problema.
--single-transaction --set ON_ERROR_STOP=on envolve toda a restauração numa única transação que é abortada ao primeiro erro. O resultado é uma base de dados completa ou uma base de dados que não foi alterada. Sem essa opção, uma falha a meio deixa uma base de dados que inicia, aceita o seu login e não contém um número desconhecido de álbuns. Esse problema pode só ser descoberto semanas mais tarde.
Agora inicie tudo.
docker compose up -d
docker compose ps
docker logs -f immich_serverAguarde uma linha de arranque como Immich Server is listening on. Depois, abra a porta 2283 e inicie sessão com as suas credenciais antigas, porque as contas de utilizador foram restauradas com o dump. Se a página de login oferecer a criação da primeira conta de administrador, a base de dados não foi restaurada. Pare e leia novamente a saída do psql.
Há um aviso sobre as instruções oficiais de restauração, que começam com docker compose down -v. -v remove volumes nomeados. No ficheiro compose padrão, UPLOAD_LOCATION e DB_DATA_LOCATION são bind mounts, por isso sobrevivem a esse comando. Se tiver alterado qualquer um deles para um volume nomeado, esse comando elimina as suas fotografias. Leia o ficheiro compose antes de o executar.
Por que a linha do tempo fica vazia depois de uma restauração
A linha do tempo é criada a partir das linhas da base de dados. O Immich nunca percorre upload/ no arranque para redescobrir fotografias, porque um ficheiro sem uma linha não tem proprietário, data nem álbum. Por isso, o problema mais comum numa restauração é ter os ficheiros de volta, mas não a base de dados. O Immich arranca, cria um esquema vazio e disponibiliza uma instância funcional sem conteúdo, enquanto o disco está cheio das suas fotografias. Nada foi perdido. Mas também nada fica visível. A correção é reproduzir o dump com o servidor parado, exatamente como acima.
A segunda variante é mais silenciosa. A base de dados é restaurada, a linha do tempo é preenchida com entradas, mas todos os recursos falham ao abrir. Isso significa que as linhas apontam para ficheiros que o contentor não consegue ver, normalmente porque library, upload e profile ficaram um nível demasiado abaixo depois de um restic restore --target /restore que ninguém colocou no local correto. Verifique a partir de dentro do contentor em vez de tentar adivinhar:
docker exec immich_server ls /dataO ficheiro compose padrão monta UPLOAD_LOCATION em /data, portanto essa listagem deve mostrar library, upload e profile. Se mostrar um diretório vazio ou uma pasta srv inesperada, a sua montagem bind aponta para o nível errado e as linhas estão corretas.
Compatibilidade de versões entre backup e restauração
O Immich lança versões com frequência, e o schema acompanha essas versões. Por isso, um dump contém o schema do servidor que o criou.
A restauração de um dump antigo num servidor mais recente normalmente funciona, porque o servidor aplica as migrações pendentes no arranque e atualiza o schema gradualmente. Esse processo é testado ao longo da sequência de versões. O problema surge quando se saltam várias versões principais de uma só vez. O projeto mantém as alterações incompatíveis nas versões principais e documenta-as no changelog.
A restauração de um dump mais recente num servidor antigo não funciona. O dump contém tabelas e colunas que o código antigo não conhece. O Immich declara que a downgrade não é suportada, mesmo entre versões de correção. Não existe um comando de rollback para utilizar.
Por isso, a restauração segura é simples. Execute exatamente a versão que criou o dump, restaure-o, inicie sessão e confirme que a linha temporal está completa. Só depois faça o upgrade. Atualize uma versão de cada vez, alterando IMMICH_VERSION e executando docker compose pull && docker compose up -d depois de cada atualização. Manter uma semana de dumps também ajuda: se o mais recente tiver sido criado durante um upgrade falhado, o de ontem continua disponível no repositório.
Verifique o backup todos os meses
Um backup que nunca foi restaurado é apenas uma suposição. Uma vez por mês, restaure-o numa instância descartável e veja uma fotografia. O teste demora cerca de vinte minutos e é a única coisa que transforma o resto desta página num plano de recuperação.
restic snapshots
restic stats latestsnapshots deve listar a execução da noite anterior. stats latest deve indicar um tamanho próximo do da sua biblioteca, não apenas alguns megabytes.
Restaure o backup num diretório temporário, idealmente num host sobressalente:
restic restore latest --target /tmp/immich-drillCopie docker-compose.yml e .env para fora do conjunto restaurado e altere três coisas na cópia. Aponte UPLOAD_LOCATION e DB_DATA_LOCATION para diretórios dentro de /tmp/immich-drill. Publique a porta Web noutro local, 12283:2283 em vez de 2283:2283. Elimine as linhas container_name:, porque o ficheiro compose fornecido define nomes fixos como immich_server. Assim, uma segunda stack no mesmo host entra em conflito com a primeira e o Docker recusa-se a criá-la.
Execute a sequência de restauração apresentada acima: apenas a base de dados, reproduza o dump e, depois, docker compose up -d. Em seguida, faça as quatro verificações que comprovam o resultado.
- Inicie sessão com a palavra-passe que utilizava antes do teste. Contas funcionais significam que o dump foi restaurado.
- Abra a linha temporal e avance até ao mês mais antigo. A existência de ativos em todo o intervalo de datas significa que todas as linhas foram restauradas, não apenas as recentes.
- Abra uma fotografia no tamanho máximo e transfira o original.
- Compare-a com o mesmo ficheiro na sua biblioteca ativa usando
sha256sum. Hashes iguais significam que os bytes sobreviveram ao percurso de ida e volta através do restic.
Em seguida, desmonte o teste com docker compose down -v no diretório do teste e elimine /tmp/immich-drill. Registe a data num local onde a veja, porque o valor deste procedimento está inteiramente em repeti-lo no mês seguinte. Se ainda estiver a decidir qual servidor de fotografias utilizar, a comparação entre PhotoPrism e Immich explica como os dois diferem precisamente neste aspeto.
FAQ
Tenho de parar o Immich para fazer uma cópia de segurança?
Pare immich_server e mantenha immich_postgres em execução. A base de dados não precisa de ser pausada, porque pg_dump lê dentro de um snapshot MVCC e vê um único instante consistente, independentemente do que estiver a escrever. Os ficheiros são o motivo para parar: o servidor grava novos uploads e o trabalho de modelo de armazenamento move ficheiros entre diretórios, pelo que uma ferramenta de cópia de segurança pode ler um ficheiro a meio de uma gravação e guardar uma cópia truncada sem qualquer erro. docker stop immich_server antes do snapshot e docker start immich_server depois dele elimina essa condição de corrida.
Posso copiar o diretório de dados do Postgres em vez de executar pg_dump?
Não. Uma cópia progressiva de um diretório de dados ativo lê ficheiros diferentes em instantes diferentes, pelo que o resultado não representa um estado consistente. O Postgres rejeita-o no arranque com PANIC: could not locate a valid checkpoint record ou falha mais tarde ao encontrar uma página danificada. Mesmo uma cópia feita com tudo parado fica associada à compilação exata da base de dados: o Immich fixa uma imagem Postgres 14 com versões específicas das extensões de pesquisa vetorial, e o diretório não abre com outra versão. Um dump SQL é texto simples e pode ser reproduzido em qualquer servidor compatível.
Por que motivo a minha linha cronológica do Immich está vazia depois de uma restauração?
Porque a linha cronológica é construída a partir de linhas da base de dados e restaurou os ficheiros sem a base de dados. O Immich nunca analisa upload/ para redescobrir fotografias, pelo que os ficheiros sem linhas permanecem invisíveis. As fotografias não foram alteradas. Pare o servidor, reproduza o dump numa instância Postgres recém-inicializada e depois inicie a stack. Se, pelo contrário, a linha cronológica estiver completa mas todas as fotografias falharem ao abrir, o problema é o inverso: library, upload e profile não estão diretamente dentro do diretório associado ao container. Verifique com docker exec immich_server ls /data.
Que diretórios do Immich posso excluir de uma cópia de segurança?
thumbs e encoded-video são regenerados a partir dos originais, e DB_DATA_LOCATION é reconstruído a partir do dump, pelo que nenhum deles precisa de estar no conjunto de cópia de segurança. Excluí-los transfere o custo para depois da restauração, em vez de o pagar em armazenamento antes dela, porque reconstruir pré-visualizações e transcodificações para uma biblioteca grande demora horas de CPU. A execução é feita em Administration > Jobs para os assets em falta. O que nunca pode excluir é library, upload e profile, que contêm a única cópia de cada original.
Posso restaurar um dump do Immich numa versão mais recente?
Normalmente, sim, porque o servidor aplica as migrações pendentes no arranque e atualiza progressivamente o esquema. O inverso falha: o Immich não suporta downgrade, nem mesmo entre versões de correção, pelo que um dump de uma versão mais recente não pode ser carregado num servidor mais antigo. Restaure com IMMICH_VERSION fixado na versão que escreveu o dump, confirme que a linha cronológica está completa e só depois faça a atualização. Registe a versão junto de cada dump com docker inspect --format '{{.Config.Image}}' immich_server, porque o valor predefinido de IMMICH_VERSION=v3 é uma tag flutuante que não fornece qualquer informação.