SSD Nodes Learn Hosting plans →
Посібники Matt ConnorВід Matt Connor · Оновлено 2026-08-13

Як зробити резервну копію Immich на VPS

Дізнайтеся, що саме копіювати в Immich v3.1.0, чому каталог Postgres не є бекапом і яка помилка під час відновлення залишає часову шкалу порожньою.

Що має містити резервна копія 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 скопіює його, і копіювання завершиться без помилки. Але це все одно не резервна копія з двох причин, через які можна побачити збій.

Перша причина — неузгоджене копіювання. Postgres спочатку записує кожну зміну в журнал попереднього запису (WAL), а потім застосовує її до файлів таблиць під час контрольної точки. Тому в будь-який момент файли на диску можуть перебувати в процесі зміни, а послідовне копіювання тривалістю чотири хвилини прочитає перший файл о 02:00, а останній — о 02:04. Ці два файли не належать до тієї самої транзакції. Коли ви запускаєте Postgres із такою копією, він або відмовляється запускатися з повідомленням PANIC: could not locate a valid checkpoint record, або запускається, а потім завершується під час першого читання пошкодженої сторінки з повідомленням invalid page in block 1234 of relation base/16384/.... Відновити дані з такої копії неможливо.

Друга причина залишається навіть у разі повної зупинки всіх процесів перед копіюванням. Каталог даних Postgres прив’язаний до точних бінарних файлів, які його записали. Immich фіксує образ бази даних за digest, наразі 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, тому дамп відтворюється в базі даних, яка вже містить об’єкти, замість зупинки на першому з них.

Тепер про деталь, яка непомітно ламає скрипти резервного копіювання. Ця команда є конвеєром, а shell повертає код завершення останньої команди в конвеєрі. Якщо 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_server

restic щоразу читає все дерево каталогів, але передає лише блоки, яких ще не було в репозиторії. Тому перший знімок передає всю вашу бібліотеку, а кожен наступний — нові фотографії за день.

Ретенція та ключі, які потрібно зберігати в іншому місці

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

forget видаляє snapshot-и з індексу. --prune видаляє дані, на які ці snapshot-и були останніми посиланнями. Запустіть 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 не підтримує пониження версії навіть між patch-релізами. Якщо новіший сервер запуститься зі старим дампом і виконає його міграції, повернутися назад буде неможливо.

Відновіть медіафайли.

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=postgres

pg_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. Тому з порожнім search path відновлення доходить до першого стовпця з типом vector, після чого psql зупиняється з помилкою ERROR: type "vector" does not exist. Додавання public до search path усуває проблему.

--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 часто випускає нові версії, і схема змінюється разом із ними. Тому дамп містить схему того сервера, який його створив.

Відновлення старого дампу на новішому сервері зазвичай працює, оскільки сервер під час запуску застосовує невиконані міграції та послідовно оновлює схему. Цей шлях перевіряється в межах послідовності релізів. Проблеми виникають, якщо за один крок пропустити кілька основних версій. Проєкт зосереджує несумісні зміни в основних релізах і документує їх у журналі змін.

Відновлення новішого дампу на старому сервері взагалі не працює. Дамп містить таблиці та стовпці, про які старий код не знає. Immich зазначає, що пониження версії не підтримується навіть між patch-релізами. Команди для відкату немає.

Тому безпечне відновлення має бути передбачуваним. Запустіть точно ту версію, яка створила дамп, відновіть дамп, увійдіть у систему та переконайтеся, що вся хронологія доступна. Лише після цього виконуйте оновлення. Оновлюйте по одному релізу за раз: змінюйте IMMICH_VERSION і після кожної зміни запускайте docker compose pull && docker compose up -d. Зберігати дампи за тиждень також корисно: якщо виявиться, що найновіший дамп створено під час невдалого оновлення, учорашній дамп усе ще буде в репозиторії.

Перевіряйте резервну копію щомісяця

Резервна копія, з якої ви ніколи не виконували відновлення, є лише припущенням. Раз на місяць відновлюйте її в тимчасовому екземплярі та переглядайте фотографію. Така перевірка займає близько двадцяти хвилин. Лише вона перетворює решту цієї сторінки на план відновлення.

restic snapshots
restic stats latest

snapshots має показувати запуск минулої ночі. 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. Тепер виконайте чотири перевірки, які підтверджують результат.

  1. Увійдіть із паролем, який використовували до перевірки. Якщо облікові записи працюють, дамп відновлено.
  2. Відкрийте часову шкалу та прокрутіть її до найстарішого місяця. Якщо ресурси охоплюють увесь діапазон дат, відновлено всі рядки, а не лише останні.
  3. Відкрийте одну фотографію в повному розмірі та завантажте оригінал.
  4. Порівняйте його з тим самим файлом у робочій бібліотеці за допомогою 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 перебудовується з дампа, тому жоден із них не потрібно включати до набору резервного копіювання. Їх пропуск означає витрати часу після відновлення замість витрат дискового простору до нього, оскільки створення попередніх переглядів і транскодованих файлів для великої бібліотеки може тривати кілька годин і навантажувати CPU. Запустити це можна через Administration > Jobs для відсутніх ресурсів. Ніколи не пропускайте library, upload і profile, оскільки саме вони містять єдину копію кожного оригіналу.

Чи можна відновити дамп Immich у новішій версії?

Зазвичай так, оскільки сервер під час запуску застосовує невиконані міграції та послідовно оновлює схему. Зворотний сценарій не працює: Immich не підтримує пониження версії, навіть між patch-релізами, тому дамп із новішого релізу не можна завантажити в старіший сервер. Виконуйте відновлення з IMMICH_VERSION, зафіксованим на релізі, який записав дамп, перевірте повноту стрічки, а потім оновіть систему. Записуйте версію поруч із кожним дампом за допомогою docker inspect --format '{{.Config.Image}}' immich_server, оскільки стандартне значення IMMICH_VERSION=v3 є плаваючим тегом і не містить корисної інформації.