Как правильно сделать бэкап и восстановление Immich
Узнайте, почему копирование каталога Postgres повреждает данные. Инструкция по созданию консистентного бэкапа Immich v3.1.0, чтобы избежать пустой ленты после восстановления.
Что должен содержать бэкап Immich
Бэкап Immich состоит из трех компонентов, зафиксированных в один момент времени. Оригиналы файлов в UPLOAD_LOCATION. SQL-дамп базы данных Postgres. Файлы .env и docker-compose.yml, описывающие стек. Восстановление подразумевает загрузку этого дампа в чистую базу данных при остановленном сервере Immich и запуск остальной части стека только после этого. Если нарушить порядок, вы получите работающий Immich, который показывает пустую ленту при заполненном диске.
Разделение важно, так как Immich хранит состояние в двух местах, которые ничего не знают друг о друге. В Postgres находятся все альбомы, кластеры лиц, общие ссылки, учетные записи пользователей, API-ключи и пути к каждому активу. В файловой системе хранятся сами пиксели. Если восстановить файлы без базы данных, Immich ничего не покажет. Если восстановить базу данных без файлов, при открытии любого актива будет отображаться битое изображение.
Приведенные здесь команды написаны для Immich v3.1.0 — версии, актуальной на начало августа 2026 года. Проект выпускает обновления быстро, и документированная процедура бэкапа менялась не один раз, поэтому проверьте версию, которую вы используете, прежде чем что-либо копировать. Если стек еще не запущен, начните с руководства по установке Immich и вернитесь сюда.
Понимание путей к файлам
Две переменные в .env определяют всё содержимое на этой странице. UPLOAD_LOCATION — это родительский каталог, в который Immich записывает все медиафайлы. DB_DATA_LOCATION — это каталог данных Postgres.
Стандартный example.env задает UPLOAD_LOCATION=./library, что является запутывающим значением по умолчанию, так как Immich затем создает папку library внутри него. Ваши оригиналы в итоге оказываются в ./library/library. Укажите абсолютный путь, чтобы скрипт резервного копирования никогда не зависел от того, из какого каталога вы его запустили.
UPLOAD_LOCATION=/srv/immich/data
DB_DATA_LOCATION=/srv/immich/postgres
DB_USERNAME=postgres
DB_DATABASE_NAME=immich
IMMICH_VERSION=v3.1.0Внутри UPLOAD_LOCATION Immich создает несколько папок. Три из них содержат данные, которые невозможно восстановить никакими задачами:
library: оригиналы, организованные согласно вашему шаблону храненияupload: оригиналы, еще не перемещенные в структуру шаблона, а также загружаемые в данный момент файлыprofile: фотографии профилей пользователей
Если вы потеряете library, фотография будет утрачена. Immich не хранит вторую копию оригинала ни в каком другом месте.
Почему копирование каталога данных Postgres не является резервной копией
DB_DATA_LOCATION выглядит как простая цель. Это обычный каталог, rsync скопирует его, и копирование завершится без ошибок. Тем не менее, это не резервная копия по двум причинам, которые вы можете наблюдать при сбое.
Первая причина — нарушение целостности данных (tearing). Postgres сначала записывает каждое изменение в журнал предзаписи (WAL), а затем применяет его к файлам таблиц во время контрольной точки (checkpoint). Таким образом, в любой момент времени файлы на диске находятся в процессе обновления, и последовательное копирование, которое занимает четыре минуты, читает первый файл в 02:00, а последний — в 02:04. Эти два файла не относятся к одной и той же транзакции. Когда вы запускаете Postgres на основе такой копии, он либо отказывается запускаться с ошибкой PANIC: could not locate a valid checkpoint record, либо запускается, но аварийно завершается при первом чтении поврежденной страницы с ошибкой invalid page in block 1234 of relation base/16384/.... Ни один из этих случаев не позволяет восстановить данные из такой копии.
Вторая причина сохраняется, даже если вы предварительно остановите все процессы. Каталог данных Postgres жестко привязан к конкретным бинарным файлам, которые его создали. Immich фиксирует образ своей базы данных по дайджесту, в настоящее время это ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0. Это Postgres 14 с двумя скомпилированными расширениями для векторного поиска. Каталог данных, созданный этой сборкой, не откроется в другой мажорной версии Postgres и не откроется в сборке с другими версиями расширений. Ваш хост для восстановления должен в точности воспроизвести этот образ. SQL-дампу это не важно: это текстовый файл, и любой совместимый сервер может его выполнить.
pg_dump полностью решает проблему нарушения целостности. Он считывает всю базу данных в рамках одного снимка MVCC (многоверсионное управление параллельным доступом), поэтому он видит базу данных именно такой, какой она была в один конкретный момент времени, в то время как другие операции записи продолжаются параллельно. Именно поэтому вам не нужно останавливать Postgres для создания дампа.
Что можно исключить из резервной копии
Эти данные генерируются повторно, поэтому их можно не копировать:
thumbs: изображения предварительного просмотра и миниатюрыencoded-video: перекодированное видеоDB_DATA_LOCATION: данные, восстанавливаемые из дампа- Docker-том
model-cache: модели машинного обучения, загружаемые повторно по запросу
Исключение этих данных — это компромисс, а не безусловное преимущество. Пересоздание миниатюр и перекодирование видео для большой библиотеки потребует часов работы процессора на небольшом VPS, а в ленте всё это время будут отображаться серые заглушки. Вы можете запустить их повторно в разделе Administration > Jobs, установив параметры "Generate Thumbnails" и "Transcode Videos" для обработки отсутствующих ресурсов. Если на целевом носителе для резервных копий достаточно места, включите их, чтобы избежать ожидания. Если вы приближаетесь к лимиту хранилища, исключите их и запланируйте пересоздание. В разделе Оценка размера библиотеки Immich описано, насколько сильно эти папки увеличиваются относительно исходных файлов.
Стоит знать еще об одной папке. В UPLOAD_LOCATION/backups хранятся автоматические дампы базы данных Immich, которые создаются ежедневно в 02:00; последние 14 копий сохраняются, настройки доступны в Administration > Settings > Backup. Они не занимают много места и действительно полезны. Однако они находятся на том же диске, что и защищаемая ими библиотека, поэтому помогают при неудачной миграции, но не при выходе сервера из строя. В любом случае создавайте собственный дамп, так как дамп, запущенный вручную, создается одновременно со снимком файлов, к которому он относится.
Создание дампа базы данных
docker exec -t immich_postgres pg_dump --clean --if-exists \
--dbname=immich --username=postgres \
| gzip > /srv/immich/backup/immich.sql.gzЗамените immich и postgres на ваши DB_DATABASE_NAME и DB_USERNAME, если вы их изменяли. --clean --if-exists добавляет DROP ... IF EXISTS перед каждым CREATE, чтобы дамп можно было развернуть в базе данных, которая уже содержит объекты, не прерываясь на первом же конфликте.
Теперь важная деталь, которая часто незаметно портит скрипты резервного копирования. Эта команда представляет собой конвейер (pipeline), а командная оболочка сообщает статус завершения последней команды в конвейере. Если pg_dump завершится с ошибкой из-за неверного пароля или остановленного контейнера, gzip получит пустой поток, создаст корректный gzip-файл и завершится с кодом 0. Ваш скрипт отчитается об успехе, а вы получите бесполезный файл размером 20 байт. Добавьте pipefail в начало каждого скрипта резервного копирования:
#!/usr/bin/env bash
set -euo pipefailЗатем проверяйте результат выполнения, а не полагайтесь на код завершения:
ls -lh /srv/immich/backup/immich.sql.gz
gunzip -c /srv/immich/backup/immich.sql.gz | head -n 3Первая строка корректного дампа содержит -- PostgreSQL database dump. Файл размером в несколько сотен байт означает, что дамп не удался, независимо от того, что сообщил скрипт.
Запишите, какая сборка создала этот файл, рядом с дампом:
docker inspect --format '{{.Config.Image}}' immich_server > /srv/immich/backup/immich-version.txtНе полагайтесь на .env для этих целей. Стандартный набор файлов использует IMMICH_VERSION=v3 — плавающий тег, который сопровождает каждый релиз 3.x, поэтому он не дает информации о том, какая именно сборка создала дамп. Зафиксируйте точный тег также в .env.
Приостановка сервера и создание снимка с помощью restic
Файлы в UPLOAD_LOCATION не являются неизменяемыми во время работы Immich. Сервер записывает новые загрузки, а задача по шаблону хранилища перемещает файлы между каталогами. Если инструмент резервного копирования начнет чтение файла в процессе записи, он сохранит эти байты как целый файл, при этом ошибки не возникнет. Остановите контейнер сервера на время выполнения операции:
docker stop immich_serverОставьте immich_postgres запущенным, так как он необходим для дампа. Веб-интерфейс и мобильное приложение будут недоступны до тех пор, пока вы снова не запустите сервер, что для домашнего экземпляра в 03:00 обычно приемлемо.
restic подходит для этой задачи, так как он выполняет дедупликацию и шифрование до того, как данные покинут сервер. Укажите репозиторий, который находится не на этом сервере:
export RESTIC_REPOSITORY=sftp:backup@backup.example.com:/srv/restic/immich
export RESTIC_PASSWORD_FILE=/root/.restic-password
restic initОбъектное хранилище работает аналогичным образом и является лучшим решением, если вы хотите полностью вынести копию за пределы вашего оборудования:
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Этой точкой доступа может быть бакет MinIO, запущенный вами на второй машине, или любой S3-совместимый провайдер. Репозиторий на том же диске, где находится библиотека, защищает только от случайного удаления и ни от чего более.
Затем создайте снимок, указав именно то, что важно:
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_serverrestic сканирует всё дерево при каждом запуске, но загружает только те блоки, которые он еще не видел. Таким образом, первый снимок переносит всю вашу библиотеку, а каждый последующий — только новые фотографии за день.
Хранение и ключи, которые должны находиться в другом месте
restic forget --prune --keep-daily 7 --keep-weekly 5 --keep-monthly 12forget удаляет снимки из индекса. --prune — это вторая часть процесса, которая удаляет данные, на которые больше не ссылается ни один снимок. Запускайте forget без --prune, иначе размер занимаемого хранилища не уменьшится.
Проверка структуры требует мало ресурсов, поэтому выполняйте её еженедельно:
restic checkЭта команда проверяет целостность метаданных репозитория. Она не считывает сами данные. Раз в месяц считывайте выборочные данные и сверяйте их с записанными хешами:
restic check --read-data-subset=5%Это единственная проверка, которая позволяет обнаружить скрытое повреждение данных на стороне хранилища, так как она загружает реальные блоки и пересчитывает их контрольные суммы. Полная проверка --read-data для библиотеки фотографий означает загрузку всего репозитория, что при использовании объектного хранилища с тарификацией по объему трафика стоит реальных денег. Поэтому на практике обычно используют проверку части данных.
Теперь часть, которую часто пропускают. Пароль от репозитория restic восстановить невозможно. Его нельзя сбросить, и служба поддержки здесь не поможет. Если единственная копия пароля хранится в /root/.restic-password на сервере, который вы пытаетесь восстановить, ваши резервные копии останутся зашифрованным набором данных. То же самое касается ключа доступа к объектному хранилищу и DB_PASSWORD из .env. Храните их там, где они не зависят от работоспособности этого сервера: распечатанными в надежном месте или в менеджере паролей на другом устройстве. Если этот менеджер также размещен на вашем сервере, он требует аналогичного подхода, а резервное копирование Vaultwarden — это отдельная задача.
Восстановление Immich в рабочем порядке
Порядок восстановления определяет, станут ли ваши резервные копии полноценными данными или пустой временной шкалой. На новом хосте придерживайтесь следующей последовательности.
Сначала восстановите конфигурацию. Она указывает, какую версию запускать и где находятся пути к данным.
restic restore latest --target /restore \
--include /srv/immich/.env \
--include /srv/immich/docker-compose.yml \
--include /srv/immich/backupЗафиксируйте версию до запуска чего-либо. Прочитайте immich-version.txt, установите IMMICH_VERSION в .env на этот конкретный тег и пока не трогайте самый свежий релиз. Immich не поддерживает понижение версии, даже между патч-релизами. Если новый сервер запустится с дампом старой версии и выполнит миграции, пути назад не будет.
Восстановите медиафайлы.
restic restore latest --target /restore --include /srv/immich/dataЗатем переместите library, upload и profile так, чтобы они находились непосредственно внутри того пути, на который указывает UPLOAD_LOCATION на этом хосте. Сам путь на хосте может измениться, так как файл compose привязывает этот каталог к фиксированному пути внутри контейнера. Структура внутри него меняться не должна.
Запустите базу данных отдельно. Оставьте DB_DATA_LOCATION пустым, чтобы Postgres инициализировал новый кластер.
cd /srv/immich
docker compose pull
docker compose create
docker start immich_postgres
docker exec immich_postgres pg_isready --username=postgrespg_isready выведет accepting connections после завершения первоначальной настройки, что занимает несколько секунд. docker compose create собирает все контейнеры, не запуская их, и в этом заключается смысл данного шага: сервер Immich пока не должен работать. Если сервер запустится с пустой базой данных, он применит миграции, создаст новую схему и предложит создать учетную запись администратора, после чего вы будете разворачивать дамп поверх работающего приложения.
Разверните дамп.
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Две части этой команды выполняют основную работу. sed необходим, так как pg_dump записывает пустой search_path в свой вывод в качестве меры безопасности, чтобы неквалифицированные имена в дампе не разрешались в неожиданную схему. Типы векторного поиска Immich находятся в public, поэтому при пустом пути поиска восстановление доходит до первого столбца, объявленного с векторным типом, и psql останавливается с ошибкой ERROR: type "vector" does not exist. Возврат public в путь поиска исправляет это.
--single-transaction --set ON_ERROR_STOP=on оборачивает всё восстановление в одну транзакцию, которая прерывается при первой же ошибке. Вы получаете либо полностью восстановленную базу данных, либо нетронутую. Без этого параметра сбой в середине процесса оставит вас с базой данных, которая запускается и принимает логин, но в которой отсутствуют неизвестные альбомы, что вы обнаружите лишь спустя недели.
Теперь запустите всё остальное.
docker compose up -d
docker compose ps
docker logs -f immich_serverДождитесь строки запуска, похожей на Immich Server is listening on, затем откройте порт 2283 и войдите под своими старыми учетными данными, так как учетные записи пользователей были восстановлены вместе с дампом. Если страница входа предлагает создать первую учетную запись администратора, значит, база данных не восстановилась. Остановитесь и еще раз изучите вывод psql.
Предупреждение об официальных инструкциях по восстановлению, которые начинаются с docker compose down -v. Команда -v удаляет именованные тома. В стандартном файле compose UPLOAD_LOCATION и DB_DATA_LOCATION являются bind mounts, поэтому они сохраняются. Если вы изменили любой из них на именованный том, эта команда удалит ваши фотографии. Прочитайте свой файл compose перед вводом команды.
Почему временная шкала пуста после восстановления
Временная шкала формируется на основе записей в базе данных. Immich никогда не сканирует upload/ при запуске для повторного обнаружения фотографий, так как у файла без записи в базе нет владельца, даты и альбома. Поэтому самая частая ошибка при восстановлении — наличие файлов при отсутствии базы данных. Immich запускается, создает пустую схему и предоставляет работающий экземпляр, в котором ничего нет, хотя диск заполнен вашими фотографиями. Данные не потеряны, но они не отображаются. Решение заключается в развертывании дампа при остановленном сервере, в точности как описано выше.
Второй вариант проблемы менее заметен. База данных восстанавливается, временная шкала заполняется записями, но при попытке открыть любой объект возникает ошибка. Это означает, что записи указывают на файлы, которые контейнер не видит. Обычно это происходит из-за того, что library, upload и profile находятся на один уровень глубже после restic restore --target /restore, который никто не переместил в нужное место. Проверьте это изнутри контейнера, вместо того чтобы строить догадки:
docker exec immich_server ls /dataСтандартный файл compose монтирует UPLOAD_LOCATION в /data, поэтому этот список должен содержать library, upload и profile. Если отображается пустая директория или лишняя папка srv, значит, ваш bind mount указывает на неверный уровень, а с записями в базе всё в порядке.
Соответствие версий при резервном копировании и восстановлении
Immich выпускает обновления часто, и схема базы данных меняется вместе с ними, поэтому дамп содержит схему той версии сервера, на которой он был создан.
Восстановление старого дампа на более новом сервере обычно проходит успешно, так как сервер применяет ожидающие миграции при запуске и обновляет схему. Этот путь тестируется в рамках последовательности релизов. Проблемы возникают при пропуске нескольких мажорных версий за один шаг; проект сохраняет критические изменения для мажорных релизов и документирует их в журнале изменений (changelog).
Восстановление более нового дампа на старом сервере не работает. Дамп содержит таблицы и столбцы, которые старый код не распознает, а Immich прямо указывает, что понижение версии (downgrade) не поддерживается даже между патч-релизами. Команды для отката не существует.
Поэтому безопасное восстановление — это рутинный процесс. Запустите именно ту версию, которая создала дамп, разверните его, войдите в систему, убедитесь, что временная шкала (timeline) полна, и только после этого приступайте к обновлению. Обновляйте по одному релизу за раз, изменяя IMMICH_VERSION и выполняя docker compose pull && docker compose up -d после каждого шага. Хранение дампов за неделю также помогает: если окажется, что самый свежий дамп был сделан во время неудачного обновления, вчерашний дамп всё ещё будет в репозитории.
Ежемесячная проверка резервных копий
Резервная копия, которую вы ни разу не восстанавливали — это лишь предположение. Раз в месяц восстанавливайте её на временном экземпляре и просматривайте фотографию. Эта процедура занимает около 20 минут и является единственным способом превратить остальную часть этой страницы в план восстановления.
restic snapshots
restic stats latestsnapshots должен отображать результат выполнения за прошлую ночь. stats latest должен показывать размер, близкий к размеру вашей библиотеки, а не несколько мегабайт.
Выполните восстановление во временный каталог, желательно на отдельном хосте:
restic restore latest --target /tmp/immich-drillСкопируйте docker-compose.yml и .env из восстановленного набора, затем измените три параметра в копии. Укажите UPLOAD_LOCATION и DB_DATA_LOCATION на каталоги внутри /tmp/immich-drill. Опубликуйте веб-порт на другом значении, например 12283:2283 вместо 2283:2283. Удалите строки container_name:, так как стандартный compose-файл жестко задает имена, такие как immich_server, из-за чего второй стек на том же хосте конфликтует с первым, и Docker отказывается его создавать.
Запустите процедуру восстановления, описанную выше: только база данных, развертывание дампа, затем docker compose up -d. Теперь выполните четыре проверки, подтверждающие работоспособность:
- Войдите в систему с паролем, который вы использовали до начала тренировки. Работающие учетные записи означают, что дамп успешно восстановлен.
- Откройте временную шкалу и прокрутите её до самого старого месяца. Наличие активов во всем диапазоне дат означает, что восстановились все строки, а не только последние.
- Откройте одну фотографию в полном размере и скачайте оригинал.
- Сравните его с тем же файлом в вашей активной библиотеке с помощью
sha256sum. Совпадающие хеши означают, что данные успешно прошли цикл через restic.
Затем завершите тренировку с помощью docker compose down -v в рабочем каталоге и удалите /tmp/immich-drill. Запишите дату там, где вы её увидите, так как ценность этой процедуры заключается исключительно в её повторении в следующем месяце. Если вы все еще выбираете, какому фотосерверу отдать предпочтение, сравнение PhotoPrism и Immich описывает, чем они отличаются именно в этом аспекте.
FAQ
Нужно ли останавливать Immich для создания резервной копии?
Остановите immich_server, но оставьте immich_postgres запущенным. Базу данных не нужно приостанавливать, так как pg_dump считывает данные в рамках одного MVCC-снимка и видит единое согласованное состояние независимо от того, какие операции записи происходят параллельно. Файлы — это причина для остановки: сервер записывает новые загрузки, а задача по шаблону хранения перемещает файлы между директориями. В результате инструмент резервного копирования может прочитать файл в процессе записи и сохранить поврежденную копию без каких-либо ошибок. docker stop immich_server перед созданием снимка и docker start immich_server после него устраняют эту проблему состояния гонки.
Можно ли скопировать папку с данными Postgres вместо запуска pg_dump?
Нет. Потоковое копирование активной директории данных считывает разные файлы в разные моменты времени, поэтому результат не является согласованным состоянием. Postgres отклонит такие данные при запуске с ошибкой PANIC: could not locate a valid checkpoint record или выйдет из строя позже из-за поврежденной страницы. Даже копия, сделанная при полностью остановленном сервере, привязана к конкретной сборке базы данных: Immich использует образ Postgres 14 с определенными версиями расширений для векторного поиска, и эта директория не откроется в другой среде. SQL-дамп представляет собой обычный текст, который можно развернуть на любом совместимом сервере.
Почему моя лента Immich пуста после восстановления?
Потому что лента формируется на основе строк в базе данных, а вы восстановили файлы без базы данных. Immich никогда не сканирует upload/ для повторного обнаружения фотографий, поэтому файлы, для которых нет записей в базе, остаются невидимыми. Сами фотографии при этом не затрагиваются. Остановите сервер, разверните дамп в свежеинициализированную базу Postgres, а затем запустите стек. Если же лента заполнена, но ни одна фотография не открывается, проблема обратная: library, upload и profile находятся не непосредственно в директории, примонтированной в контейнер. Проверьте это с помощью docker exec immich_server ls /data.
Какие папки Immich можно исключить из резервной копии?
thumbs и encoded-video восстанавливаются из оригиналов, а DB_DATA_LOCATION пересоздается из дампа, поэтому их не обязательно включать в резервную копию. Исключение этих папок экономит место при хранении, но требует времени после восстановления, так как пересоздание превью и транскодирование для большой библиотеки занимает часы работы процессора и запускается через Administration > Jobs для отсутствующих ресурсов. Что нельзя исключать ни при каких обстоятельствах, так это library, upload и profile, где хранятся единственные копии всех оригиналов.
Можно ли восстановить дамп Immich в более новую версию?
Обычно да, так как сервер применяет ожидающие миграции при запуске и обновляет схему. Обратный процесс невозможен: Immich не поддерживает понижение версии, даже между патч-релизами, поэтому дамп из более новой версии нельзя загрузить в старый сервер. Выполняйте восстановление с IMMICH_VERSION, зафиксированным на версии, в которой был создан дамп, убедитесь в целостности ленты, а затем обновляйтесь. Записывайте версию рядом с каждым дампом с помощью docker inspect --format '{{.Config.Image}}' immich_server, так как стандартный тег IMMICH_VERSION=v3 является плавающим и не содержит информации о версии.