Личный фотоархив растёт быстрее любого сервера: за пять-семь лет семейной съёмки легко набегает 200–500 ГБ оригиналов, а вместе с ними — вопросы о том, где эти файлы хранятся, кто имеет к ним доступ и что будет, если облачный аккаунт заблокируют или тариф пересчитают. Именно на этой боли вырос Immich — открытая платформа для хранения и управления фото- и видеотекой, которую официальный репозиторий описывает как «high performance self-hosted photo and video management solution» под лицензией AGPL-3.0.
Масштаб доверия к проекту измеряется не маркетингом, а цифрами: на 9 октября 2026 года у репозитория 115 792 звезды и 7 152 форка, а актуальный стабильный релиз — Immich v3.3.1, опубликованный 8 октября 2026 года (без статуса pre-release). За сутки до написания статьи вышли исправления для совместного доступа к людям на фото и прокрутки на мобильных устройствах, а также новый индикатор, показывающий, с кем поделён тот или иной человек из распознанных.
Ниже — практический разбор развертывания Immich v3 на виртуальном сервере: не «нажмите docker compose up», а инженерная работа с требованиями к железу, лимитами контейнеров, реверс-прокси и резервным копированием библиотеки. Отдельно разберём два сбоя, на которые чаще всего натыкаются администраторы: Illegal instruction из-за микроархитектуры процессора и OOM Killer, убивающий Postgres.
1. Что даёт Immich v3 и кому он действительно нужен
Immich — это приложение-хранилище, а не «ещё одна галерея». Базовая логика такая: на VDS поднимается сервер, в мобильном приложении включается автобэкап, и все новые снимки автоматически уезжают на ваш диск. Дальше работает веб-интерфейс с таймлайном, альбомами, поиском и совместным доступом для членов семьи.
Из коробки платформа закрывает четыре группы задач:
- Хранение и синхронизация: оригиналы фото и видео, автобэкап с Android и iOS, догрузка существующей библиотеки, внешние библиотеки (External Libraries) для уже разложенных по папкам архивов.
- Организация: таймлайн, альбомы, избранное, архивы, хранилище-шаблоны имён файлов (Storage Template) с миграцией уже загруженных ассетов.
- Поиск на базе машинного обучения: умный поиск по содержимому кадра, распознавание и группировка лиц, OCR-распознавание текста на снимках. Все эти модели живут в отдельном контейнере
immich-machine-learning. - Совместное использование: несколько учётных записей, партнёрский доступ, публичные ссылки на альбомы, а с версии v3.3.1 — явный индикатор того, с кем именно поделён распознанный человек.
⚠️ Важное предупреждение: три официальных ограничения
Первое: Immich не поддерживает работу на подпути вида example.com/immich — сервис обязан обслуживаться в корне домена или поддомена. Второе: официальная документация не рекомендует запускать Docker внутри LXC-контейнеров (OpenVZ/LXC-виртуализация) — нужна полноценная KVM-машина. Третье: сервер Immich совместим только с совпадающей мажорной версией мобильного приложения, поэтому клиенты обновляют раньше сервера.
Отдельно стоит понять экономику решения. Вы не платите за место в чужом облаке, но платите за диск, память и трафик на своём сервере. Прикидка простая: библиотека оригиналов плюс 10–20 % сверху на превью и транскоды — это официальная цифра из документации Immich, а не оценка «на глаз». К этому добавляется база данных Postgres размером 1–3 ГБ и автоматические дампы.
💡 Совет инженера
Перед арендой VDS посчитайте не «сколько фото», а сколько места займёт вся система: оригиналы + 10–20 % на превью и перекодированное видео + 1–3 ГБ на Postgres + место под 14 автоматических дампов базы (значение по умолчанию). На диске 40 ГБ библиотека на 200 ГБ не поместится, и это выяснится ровно на середине первой загрузки.
Если сейчас документы у вас живут отдельно от фотографий, посмотрите на смежный разбор — электронный архив документов на Paperless-ngx в Docker Compose: связка «Immich для медиа + Paperless-ngx для сканов» закрывает домашний архив целиком, а настраиваются оба сервиса по одной и той же логике.
2. Требования Immich v3 и подбор VDS: RAM, ядра, x86-64-v2 и диск
Самая частая ошибка при развертывании — арендовать сервер «как под обычный сайт», на 1–2 ГБ памяти. Immich — не сайт: внутрь входят четыре контейнера (сервер, машина машинного обучения, Postgres, Redis/Valkey), и каждый требует своей доли ресурсов. Ниже — официальные требования из документации Immich, обновлённой 8 октября 2026 года.
| Ресурс | Минимум | Рекомендуется | Комментарий |
|---|---|---|---|
| RAM | 6 ГБ | 8 ГБ | На 4 ГБ запуск возможен только с отключёнными функциями машинного обучения |
| CPU | 2 ядра | 4 ядра | Индексация и распознавание лиц упираются в ядра, а не в диск |
| Архитектура | amd64 или arm64 |
x86-64-v2 | Начиная с v3 контейнер ML на amd64 требует микроархитектуру не ниже x86-64-v2 |
| ОС | Linux 64-bit | Ubuntu / Debian LTS | Полноценная KVM-машина: Docker в LXC официально не рекомендован |
| Диск под БД | локальный SSD | локальный NVMe | Файлы Postgres занимают 1–3 ГБ; сетевые хранилища не поддерживаются |
| ПО | Docker + Compose v2 | Docker Engine 25+ | Команда — только docker compose; устаревший docker-compose v1 не поддерживается |
Ключевое уточнение по памяти, которое легко пропустить: если вы ограничиваете контейнеры через лимиты Docker, базе данных требуется минимум 2 ГБ RAM — это прямое требование документации. То есть на сервере с 4 ГБ памяти под сам сервер Immich, ML-контейнер и операционную систему остаётся около двух гигабайт, и любой всплеск индексации приводит к срабатыванию OOM Killer. Восемь гигабайт — не «жирно», а рабочий минимум для библиотеки в сотни гигабайт.
Проверка x86-64-v2 до покупки сервера
Уровень x86-64-v2 требует от процессора набора инструкций, который появился в массовых CPU примерно с 2012 года. Если виртуальная машина отдаёт «урезанный» тип процессора, контейнер машинного обучения падает с ошибкой Illegal instruction, а последней версией Immich, работавшей на x86-64-v1, официально остаётся v2.7.5 — и она больше не поддерживается.
Проверить свой сервер можно без установки чего-либо, по флагам процессора:
# x86-64-v2 требует CMPXCHG16B (cx16), POPCNT и SSE4.2
for flag in cx16 popcnt sse4_2; do
if grep -qw "$flag" /proc/cpuinfo; then
echo "OK: $flag"
else
echo "НЕТ: $flag — процессор не поддерживает x86-64-v2"
fi
done
Если хотя бы одного флага нет — Immich v3 на этом сервере не запустится. Решается либо сменой типа процессора в панели провайдера (KVM позволяет выставить режим с полным набором инструкций хоста), либо переходом на другую площадку. Проверить железо арендованного сервера целиком помогает lscpu, а доступность внешних портов и IP — утилиты определения IP-адреса и онлайн-сканера портов.
Сколько места занять: расчёт на реальной библиотеке
Возьмём типичный сценарий — 200 ГБ оригиналов фото и видео. Прибавляем 10–20 % на превью и перекодированные видео, получаем 220–240 ГБ, плюс база Postgres на 1–3 ГБ, плюс автоматические дампы. Итого под систему нужен диск от 300 ГБ, причём под базу данных — обязательно локальный SSD/NVMe.
| Что размещаем | Объём | Где хранить |
|---|---|---|
| Оригиналы | 200 ГБ | UPLOAD_LOCATION/upload |
| Превью и транскоды | +10–20 % (~30 ГБ) | UPLOAD_LOCATION/thumbs, encoded-video |
| База Postgres | 1–3 ГБ | DB_DATA_LOCATION — только локальный SSD |
| Автодампы БД | до 14 копий | UPLOAD_LOCATION/backups |
💡 Совет инженера
Разделяйте хранение: базу данных держите на системном NVMe (ей нужны быстрые случайные операции), а библиотеку оригиналов — на отдельном томе или диске. Тогда рост фототеки не заставит вас расширять системный раздел, а перенос библиотеки сведётся к правке одной переменной UPLOAD_LOCATION.
Хостинг для такой задачи выбирают по трём критериям: KVM-виртуализация, поддержка x86-64-v2 и NVMe-накопители. Как сравнивать тарифы и на что смотреть в описании провайдера, разобрано в материале как выбрать надёжный VDS для сайта в 2026 году, а проверить площадку по внешним признакам можно через проверку хостинга.
3. Подготовка Ubuntu 24.04 LTS: Docker Engine, отдельный диск, Swap и UFW
Начинаем с чистого сервера. Первый шаг — базовая гигиена доступа: обновление пакетов, вход по SSH-ключу и запрет парольной аутентификации. Полный чек-лист изложен в отдельном материале — как защитить новый VDS в первые 15 минут, здесь же ограничимся тем, что нужно именно для Immich.
# Обновляем систему и индекс пакетов
sudo apt update && sudo apt upgrade -y
Установка Docker Engine из официального репозитория
Это не формальность. В документации Immich отдельно предупреждают: если использовать пакет docker.io из репозитория Ubuntu, команда запуска падает с ошибками вида unknown shorthand flag: 'd' in -d или open .env: permission denied — под этим именем поставляется старый Docker без актуального плагина Compose. Ставим Docker из официального репозитория разработчика, причём в актуальном формате подключения репозитория DEB822.
# 1. Официальный GPG-ключ Docker и базовые утилиты
sudo apt update
sudo apt install -y ca-certificates curl gnupg wget htop
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# 2. Подключение репозитория: создаём файл в актуальном формате DEB822
sudo nano /etc/apt/sources.list.d/docker.sources
Содержимое файла /etc/apt/sources.list.d/docker.sources для Ubuntu 24.04 LTS на архитектуре amd64:
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: noble
Components: stable
Architectures: amd64
Signed-By: /etc/apt/keyrings/docker.asc
💡 Совет инженера
Два поля стоит подставить под свою систему, а не копировать вслепую: Suites — кодовое имя дистрибутива (получить его можно командой . /etc/os-release && echo "$VERSION_CODENAME", для 24.04 LTS это noble) и Architectures — архитектура процессора (dpkg --print-architecture, обычно amd64). Раньше репозиторий подключали одной строкой с deb [...] в файле docker.list; формат DEB822, показанный выше, — текущий официальный способ.
# 3. Установка Engine, containerd и плагинов Compose/Buildx
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# 4. Проверка версий
docker --version
docker compose version
Версия docker compose должна быть второго поколения (плагин, вызывается через пробел), а Engine — не ниже 25: официальный docker-compose.yml использует параметр start_interval в healthcheck базы, и на более старых движках запуск завершится ошибкой can't set healthcheck.start_interval as feature require Docker Engine v25 or later.
⚠️ Важное предупреждение: группа docker равна root
Добавление пользователя в группу docker даёт ему фактически права суперпользователя: через Docker можно смонтировать корневую файловую систему хоста внутрь контейнера. Если добавляете себя в группу — применяйте изменение через newgrp docker (или перелогиньтесь) и осознавайте уровень доступа. Для боевого сервера с фотоархивом разумнее выполнять команды через sudo docker compose, не расширяя права учётной записи.
Разметка дисков: библиотека отдельно, база отдельно
Определяем, какие устройства доступны, и монтируем дополнительный диск под библиотеку. База данных останется на системном NVMe — это прямое требование документации Immich: сетевые хранилища и медленные диски под DB_DATA_LOCATION не поддерживаются.
# Смотрим блочные устройства и файловые системы
lsblk -f
# Разметка дополнительного диска (пример: /dev/vdb) и его UUID
sudo mkfs.ext4 -L immich-data /dev/vdb
sudo blkid /dev/vdb
# Точки монтирования: 750 — владелец пишет, остальные не читают
sudo install -d -m 750 /mnt/immich-data
sudo install -d -m 750 /var/lib/immich
Далее добавляем запись в /etc/fstab, подставив собственный UUID из вывода blkid, и проверяем монтирование без перезагрузки:
# /etc/fstab — монтирование по UUID (подставьте свой идентификатор)
UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx /mnt/immich-data ext4 defaults,noatime 0 2
# Применяем без перезагрузки и проверяем результат
sudo systemctl daemon-reload
sudo mount -a
df -h /mnt/immich-data
💡 Совет инженера
Опция noatime в fstab отключает запись времени последнего доступа к файлам. Для фототеки, которую читают редко, а пишут постоянно, это заметно снижает лишние операции записи на диск и продлевает жизнь SSD.
Swap: страховка от OOM Killer
Immich — многоконтейнерное приложение, и пик потребления даёт не сервер, а индексация: генерация превью, умный поиск, распознавание лиц. При 8 ГБ памяти Swap нужен как амортизатор, а не как «дополнительная память». Создаём файл подкачки 4 ГБ с правами 600:
# Создание и активация Swap-файла на 4 ГБ
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
# Автозагрузка и умеренная агрессивность вытеснения
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-swappiness.conf
sudo sysctl --system
# Проверка
swapon --show
free -h
Почему vm.swappiness=10, а не 60 по умолчанию: при агрессивном вытеснении ядро отправляет в Swap активно используемые страницы Postgres, и база начинает «тормозить» на ровном месте. Как правильно считать размер подкачки и когда вместо файла стоит использовать zRAM, подробно разобрано в статье правильная настройка Swap и zRAM на VDS.
Фаервол и ловушка Docker с обходом UFW
# Запрещаем входящие по умолчанию, оставляем только SSH до включения
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp comment 'SSH'
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'
sudo ufw enable
sudo ufw status verbose
⚠️ Важное предупреждение: UFW не защищает порты Docker
Docker управляет правилами iptables напрямую и вставляет свои цепочки раньше фильтров UFW. Порт, опубликованный в compose как 2283:2283, окажется доступен из интернета, даже если ufw status показывает запрет входящих. Именно поэтому в следующем разделе мы публикуем порт Immich только на локальном интерфейсе 127.0.0.1. Механика обхода и способы защиты разобраны в статье Docker обходит UFW на Linux.
Проверить, что порт действительно не виден извне, можно до и после развертывания через сканер портов онлайн — сканирование выполняется с внешнего узла, а не с самого сервера, что исключает ложное «всё закрыто».
4. Развертывание Immich v3.3.1: манифест Docker Compose, .env и лимиты ресурсов
Работаем в каталоге /opt/immich. Первый шаг — скачать официальные файлы именно той версии, которую мы фиксируем, а не «последнюю с ветки main»: документация Immich прямо предупреждает, что compose-файл из основной ветки может быть несовместим с текущим релизом.
sudo install -d -m 750 /opt/immich
cd /opt/immich
# Официальный манифест и шаблон переменных окружения для конкретного релиза
sudo wget -O docker-compose.yml https://github.com/immich-app/immich/releases/download/v3.3.1/docker-compose.yml
sudo wget -O .env https://github.com/immich-app/immich/releases/download/v3.3.1/example.env
Заполняем .env: пути, версия и пароль базы
Переменные UPLOAD_LOCATION и DB_DATA_LOCATION читает не сам контейнер, а Docker Compose при подстановке томов. Пароль базы генерируем криптостойко и без спецсимволов: документация Immich допускает в DB_PASSWORD только символы A–Z, a–z и 0–9, иначе Docker может некорректно разобрать значение.
# Криптостойкий пароль без спецсимволов (только hex-символы)
openssl rand -hex 32
# /opt/immich/.env
# Каталог с оригиналами, превью и дампами — на отдельном диске
UPLOAD_LOCATION=/mnt/immich-data/library
# Каталог базы Postgres — на локальном NVMe системного диска
DB_DATA_LOCATION=/var/lib/immich/postgres
# Часовой пояс: используется exiftool как fallback и для планировщика дампов
TZ=Europe/Moscow
# Фиксируем точную версию вместо метатега v3 — предсказуемые обновления
IMMICH_VERSION=v3.3.1
# Пароль базы: подставьте результат openssl rand -hex 32
DB_PASSWORD=ВСТАВЬТЕ_СГЕНЕРИРОВАННЫЙ_КЛЮЧ
# Значения ниже менять не требуется
DB_USERNAME=postgres
DB_DATABASE_NAME=immich
Production-манифест: закрываем порт и ограничиваем ресурсы
Ниже — официальный манифест версии v3.3.1 с двумя осознанными изменениями. Первое: порт публикуется только на 127.0.0.1, чтобы обойти правило iptables, которое Docker добавляет в обход UFW. Второе: каждому сервису заданы лимиты mem_limit и cpus — это сервисные ключи Compose: mem_limit ограничивает объём памяти контейнера (строка вида 2g), а cpus задаёт число выделяемых ядер и допускает дробные значения. Секция deploy в спецификации Compose помечена как опциональная и реализуется не всеми инструментами, поэтому для предсказуемого результата мы используем именно ключи уровня сервиса. Ограничение для базы в 2 ГБ — не произвольная цифра, а официальное требование Immich при использовании лимитов Docker.
# /opt/immich/docker-compose.yml
name: immich
services:
immich-server:
container_name: immich_server
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
volumes:
- ${UPLOAD_LOCATION}:/data
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
ports:
# Было '2283:2283' — публикация на 0.0.0.0 в обход UFW.
# Теперь порт слушает только loopback, наружу его отдаёт реверс-прокси.
- '127.0.0.1:2283:2283'
depends_on:
- redis
- database
restart: always
mem_limit: 2g
cpus: 2
healthcheck:
disable: false
immich-machine-learning:
container_name: immich_machine_learning
image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
volumes:
- model-cache:/cache
env_file:
- .env
restart: always
mem_limit: 2g
cpus: 2
healthcheck:
disable: false
redis:
container_name: immich_redis
image: docker.io/valkey/valkey:9@sha256:c123e3715db63d06d4ad6964884037aa0d5d4d703939b9929954112889708e1d
healthcheck:
test: redis-cli ping | grep -q PONG || exit 1
restart: always
mem_limit: 256m
cpus: 1
database:
container_name: immich_postgres
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:bcf63357191b76a916ae5eb93464d65c07511da41e3bf7a8416db519b40b1c23
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_DB: ${DB_DATABASE_NAME}
POSTGRES_INITDB_ARGS: '--data-checksums'
# База на SSD (значение по умолчанию); для HDD раскомментируйте строку ниже
DB_STORAGE_TYPE: 'SSD'
volumes:
- ${DB_DATA_LOCATION}:/var/lib/postgresql/data
shm_size: 128mb
restart: always
mem_limit: 2g
cpus: 2
healthcheck:
disable: false
volumes:
model-cache:
💡 Совет инженера
Образы базы и кэша в официальном манифесте закреплены по digest (@sha256:...), а не по плавающему тегу. Это защита от ситуации, когда пересборка контейнеров внезапно подтягивает другую версию СУБД. Сохраняйте эту практику: digest меняйте только осознанно, вместе с чтением release notes.
Проверяем конфигурацию и запускаем стек. Отдельно обратите внимание на итоговые лимиты: 2 ГБ серверу, 2 ГБ ML-контейнеру, 2 ГБ базе и 256 МБ кэшу — это 6,25 ГБ, что оставляет операционной системе на 8-гигабайтном сервере необходимый запас. Механику расчёта лимитов и защиту от каскадных падений контейнеров разбирает материал лимиты памяти и CPU в Docker Compose.
cd /opt/immich
# Проверка синтаксиса без запуска
sudo docker compose config --quiet
# Загрузка образов и старт в фоне
sudo docker compose pull
sudo docker compose up -d
# Статус контейнеров: healthcheck покажет состояние каждого сервиса
sudo docker compose ps
# Логи сервера на предмет ошибок миграций и ML
sudo docker compose logs --tail=100 immich-server
Первый вход: регистрация администратора
Порт закрыт снаружи, поэтому в веб-интерфейс заходим через SSH-туннель с рабочей машины — этот же приём пригодится, пока не настроен реверс-прокси с HTTPS:
# На локальной машине: пробрасываем порт 2283 на сервер
ssh -L 2283:127.0.0.1:2283 sysadmin@IP_ВАШЕГО_СЕРВЕРА
Теперь открываем http://localhost:2283 и нажимаем «Getting Started»: первый зарегистрированный пользователь автоматически становится администратором и получает право создавать другие учётные записи. Сразу после регистрации имеет смысл задать шаблон хранения файлов (Storage Template) — по умолчанию это Year/Year-Month-Day/Filename.Extension, и включение шаблона до массовой загрузки избавит от последующей миграции десятков тысяч файлов.
⚠️ Важное предупреждение: не трогайте файлы в библиотеке вручную
Документация Immich описывает это максимально жёстко: изменять или удалять файлы внутри upload, library, thumbs и encoded-video нельзя — иначе в базе останутся ссылки на несуществующие ассеты, и восстановить целостность будет крайне сложно. Единственный путь управления файлами — веб-интерфейс или мобильное приложение.
Если сервер Immich получает некорректный IP клиента в логах и на страницах статистики, в .env добавьте переменную IMMICH_TRUSTED_PROXIES со списком IP доверенных прокси через запятую. Учтите официальное предупреждение: изменение переменных окружения не применяется простым рестартом — контейнеры нужно пересоздать командой sudo docker compose up -d --force-recreate.
5. Reverse proxy и HTTPS: Caddy, Nginx и доступ только через VPN
Immich не умеет сам выпускать TLS-сертификаты, и это осознанное архитектурное решение: шифрованием занимается прокси перед ним. Документация формулирует требования к любому прокси однозначно — он обязан пробрасывать заголовки Host, X-Real-IP, X-Forwarded-Proto и X-Forwarded-For, а также разрешать загрузку крупных файлов. Иначе мобильное приложение будет отваливаться, а в интерфейсе появятся «серые» превью.
Вариант 1: Caddy с автоматическим выпуском сертификата
Самый короткий путь. Caddy сам получает и продлевает сертификаты Let's Encrypt, поэтому конфигурация сводится к трём строкам. Прокси ставим на хост и направляем на закрытый порт 127.0.0.1:2283:
# /etc/caddy/Caddyfile
photos.example.com {
reverse_proxy 127.0.0.1:2283
}
# Проверка конфигурации и перезагрузка
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
sudo systemctl status caddy --no-pager
Caddy автоматически проксирует WebSocket-соединения (они нужны интерфейсу для живых обновлений) и не требует ручной настройки лимитов на размер тела запроса. Подробный разбор этого веб-сервера, включая HTTP/3 и работу с Docker, есть в отдельном руководстве — Caddy 2 вместо Nginx с автоматическим SSL.
Вариант 2: Nginx по официальному примеру
Nginx даёт больше контроля, но требует явно назвать таймауты и лимиты. Значения ниже взяты из официальной документации Immich: буферизация загрузок отключена, чтобы прокси не съедал память и не тормозил выгрузку, а тело запроса разрешено до 50000 МБ — иначе видео с телефона не загрузится.
server {
listen 80;
listen [::]:80;
server_name photos.example.com;
# Крупные загрузки: лимит и буферизация по официальной рекомендации
client_max_body_size 50000M;
proxy_request_buffering off;
client_body_buffer_size 1024k;
# Заголовки, обязательные для корректной работы Immich
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket и длительные загрузки видео
proxy_http_version 1.1;
proxy_redirect off;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
send_timeout 600s;
location / {
proxy_pass http://127.0.0.1:2283;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
⚠️ Важное предупреждение: порядок выпуска сертификата
Не вписывайте пути к сертификату в конфигурацию вручную до его выпуска — тест конфигурации упадёт, потому что файлов ещё не существует. Правильный порядок: сначала создайте блок только на порту 80, проверьте его командой sudo nginx -t, затем выполните sudo certbot --nginx --redirect -d photos.example.com. Certbot сам добавит в конфиг директивы ssl_certificate с путями к выпущенным файлам и настроит перенаправление с HTTP на HTTPS: для команд run и install редирект включается по умолчанию, а флаг --redirect фиксирует его явно и не даёт настройке потеряться при последующих правках.
# 1. Проверяем блок только с HTTP
sudo nginx -t
# 2. Выпускаем сертификат и включаем редирект на HTTPS
sudo certbot --nginx --redirect -d photos.example.com
# 3. Повторная проверка конфигурации после правок Certbot
sudo nginx -t && sudo systemctl reload nginx
Перед выпуском сертификата убедитесь, что A-запись домена указывает на IP сервера и уже разошлась по резолверам — иначе Let's Encrypt не сможет пройти проверку. Проверить актуальные значения DNS удобно через DNS lookup онлайн, а итоговую цепочку сертификата и срок его действия — через проверку SSL-сертификата.
Вариант 3: вообще не открывать веб-порты
Для приватного фотоархива логично не публиковать сервис в интернете совсем. В этом случае 80 и 443 остаются закрытыми, а доступ идёт через оверлейную сеть: клиент подключается к Immich по внутреннему адресу http://100.x.x.x:2283. Единственный нюанс — веб-интерфейс будет отдаваться по HTTP, и встроенная политика безопасности Immich попытается апгрейдить запросы до HTTPS. Документация описывает решение: собственный файл конфигурации helmet с отключённым правилом upgrade-insecure-requests и переменная IMMICH_HELMET_FILE с путём к нему.
Как поднять собственную оверлейную сеть на WireGuard-совместимом координаторе, разобрано в статье свой Tailscale на VDS: развертывание Headscale. Там же — про доступ к базам данных и админкам без проброса портов наружу.
💡 Совет инженера
Что бы вы ни выбрали, перед публикацией домена прогоните внешнее сканирование: порт 2283 не должен отвечать с интернета, а открытыми обязаны остаться только 80/443 (или ни одного, если используете VPN). Проверить это можно утилитой проверки портов онлайн — запрос выполняется с внешнего узла, а не с самого сервера.
Отдельно стоит помнить о каверзе с проверкой домена: если прокси использует http-01 challenge, путь /.well-known/immich должен корректно доходить до сервиса Immich, иначе мобильное приложение столкнётся с ошибками подключения. В конфигурации Nginx это отдельная локация, закомментированная в официальном примере.
Рекомендуемые VDS-провайдеры
Отказоустойчивые сервера с быстрыми NVMe и каналом до 1 Гбит/с
Timeweb Cloud — Быстрые NVMe VDS
Высокочастотные ядра ускоряют индексацию библиотеки и работу моделей машинного обучения, а NVMe-накопители обеспечивают быстрый отклик базы Postgres.
Selectel — Корпоративная надежность Tier III
Гарантированные ресурсы vCPU и отказоустойчивая инфраструктура в дата-центрах Tier III — подходят для семейного архива, который должен быть доступен круглосуточно.
Beget — Простота и отказоустойчивость
Развертывание Docker в один клик, удобный терминал и бесплатные автоматические бэкапы сервера — удобная стартовая площадка для первого self-hosted фотоархива.
6. Обслуживание и обновления: версии, Illegal instruction и OOM Killer
Immich обновляется часто, и порядок действий здесь строго регламентирован разработчиками. Перед каждым обновлением нужно прочитать release notes и учесть breaking changes — отдельная ветка обсуждений в репозитории собирает именно такие релизы. Дальше обновление сводится к двум командам в каталоге с манифестом:
cd /opt/immich
# 1. Правим IMMICH_VERSION в .env на новую версию
sudo nano .env
# 2. Загружаем новые образы и перезапускаем сервисы
sudo docker compose pull && sudo docker compose up -d
# 3. Удаляем устаревшие образы, освобождая место
sudo docker image prune
Политика версий: почему откат невозможен
Проект следует семантическому версионированию, и это важно для планирования. Все breaking changes, включая изменения API и схемы развертывания, допускаются только в мажорных релизах. При этом сервер Immich совместим лишь с совпадающей мажорной версией мобильного приложения — поэтому клиенты на телефонах обновляют раньше сервера. Патчи в старые ветки не бэкпортируются, а понижение версии не поддерживается даже в пределах одной минорной ветки.
Практический вывод: не отключайте фиксацию версии ради метатега v3, если сервер обслуживает семейный архив. Метатег удобен, но принесёт мажорное обновление автоматически, и вы узнаете о breaking changes из упавшего сервиса, а не из release notes. Метатеги, кстати, не следуют за release-кандидатами — это официально оговорено.
⚠️ Важное предупреждение: перенос старой инсталляции
Если вы разворачиваете Immich поверх базы, созданной на старом образе tensorchord/pgvecto-rs, потребуется миграция на расширение VectorChord с образом ghcr.io/immich-app/postgres. После такой миграции понижение Immich ниже 1.133.0 запрещено, а резервные копии, снятые после перехода, восстанавливаются только образом, содержащим VectorChord. Свежая установка, описанная выше, этой проблемы лишена.
Ошибка Illegal instruction: микроархитектура процессора
Симптом обманчив: сервер Immich работает, а контейнер машинного обучения уходит в постоянный перезапуск, в логах — Illegal instruction. Причина не в образе, а в процессоре виртуальной машины: начиная с v3 ML-контейнер на amd64 требует уровня x86-64-v2.
# Что показывает виртуалка про свой процессор
lscpu | grep -E 'Model name|Flags' | head -n 2
# Проверяем три обязательных флага x86-64-v2
for flag in cx16 popcnt sse4_2; do
grep -qw "$flag" /proc/cpuinfo && echo "OK: $flag" || echo "НЕТ: $flag"
done
# Ошибки контейнера машинного обучения
sudo docker compose logs --tail=50 immich-machine-learning
Если флагов нет — вариантов два. Первый: сменить тип процессора виртуальной машины в панели провайдера на режим с полным набором инструкций хоста (в KVM это штатная операция, но зависит от политики хостера). Второй: переехать на другую площадку. Оставаться на v2.7.5 не стоит — эта версия официально не поддерживается и не получает исправлений безопасности.
OOM Killer убивает Postgres: как распознать и что делать
Вторая частая авария — «сервер жив, а фото не открываются». Первым обычно погибает контейнер базы: ядро выбирает процесс-«обжору» по счёту badness, и Postgres с большим RSS становится удобной жертвой. Признаки видны сразу в системном журнале:
# Ищем факты срабатывания OOM Killer
sudo dmesg -T | grep -i -E 'oom|killed process'
# Кто сколько памяти занимает прямо сейчас
sudo docker stats --no-stream
# Историю перезапусков контейнеров
sudo docker compose ps
Дальнейшие действия — в порядке приоритета. Сначала убедитесь, что лимиты mem_limit заданы каждому сервису: без них один контейнер может выесть всю память сервера. Затем проверьте, что база действительно получила свои 2 ГБ — официальное требование при использовании лимитов Docker. И только после этого снижайте нагрузку от фоновых задач: тяжёлые очереди (генерация превью, умный поиск, распознавание лиц) можно запускать вручную и порциями из раздела Administration → Job Queues, а не позволять им идти сплошным потоком сразу после загрузки тысяч файлов.
💡 Совет инженера
Не диагностируйте нехватку памяти «на глаз» по free -h в момент спокойствия. OOM Killer срабатывает на пике — во время индексации библиотеки. Настройте внешний мониторинг с алертами в Telegram (например, по разбору легковесного мониторинга серверов и Docker в Beszel) и смотрите на график потребления в момент первой массовой загрузки фото.
Если сервер обслуживает сайт рядом с Immich, полезно заранее проверить, кто именно съедает диск и память: разбор типичных пожирателей места есть в статье закончилось место на диске VDS, а инструменты внешней проверки доступности и SSL-сроков собраны в материале про круглосуточный мониторинг доступности сайтов и сертификатов.
7. Бэкапы: официальный порядок, дампы Postgres и копия в S3
В отличие от документов и заметок, фотоархив нельзя «перегенерировать». При этом Immich устроен так, что база данных хранит пути к файлам и все метаданные: альбомы, лица, историю, пользователей. Из этого следуют два независимых объекта резервного копирования — база и файловое хранилище, и потеря любого из них делает архив нерабочим.
Что платформа делает сама
Immich автоматически создаёт дампы базы для аварийного восстановления: они лежат в UPLOAD_LOCATION/backups, а расписание и срок хранения настраиваются в Administration → Settings → Database Dump Settings. По умолчанию система хранит последние 14 копий и создаёт дамп ежедневно в 2:00. Ручной дамп можно инициировать из Administration → Job Queues, выбрав задачу создания дампа базы.
⚠️ Важное предупреждение: дамп базы — это не бэкап архива
Автоматические дампы содержат только метаданные и не включают фотографии и видео. Их обязательно нужно использовать вместе с копией файлов из UPLOAD_LOCATION. И наоборот: копия файлов без базы превращается в набор нечитаемых каталогов с хеш-именами.
Ручной дамп базы: команда из документации
Официальная команда снимает логический дамп через pg_dump внутри контейнера и сразу сжимает его. Пароль в аргументах не передаётся — соединение идёт по локальному сокету внутри контейнера базы, поэтому секрет не попадёт в историю команд или в вывод ps aux:
# Официальный вариант дампа.
# immich и postgres — значения по умолчанию для DB_DATABASE_NAME и DB_USERNAME:
# подставьте свои, если вы их меняли в .env
docker exec -t immich_postgres pg_dump --clean --if-exists \
--dbname=immich --username=postgres | gzip > "/mnt/immich-data/manual-dumps/immich_$(date +%F).sql.gz"
Порядок копирования: сначала база, потом файлы
Документация Immich описывает проблему рассинхронизации прямо: если снимать копии в произвольном порядке, после восстановления возможны «битые» ассеты. Идеальный вариант — остановить контейнер сервера на время бэкапа, тогда база и файлы гарантированно совпадают. Если остановка неприемлема, порядок строго такой: сначала база, затем файловая часть. В худшем случае на диске останутся файлы, о которых база не знает, и их можно будет догрузить вручную. Обратный порядок приводит к ссылкам на отсутствующие файлы.
Критичными для восстановления являются три каталога: UPLOAD_LOCATION/library, UPLOAD_LOCATION/upload и UPLOAD_LOCATION/profile. Каталоги thumbs и encoded-video можно не копировать — но после восстановления придётся заново прогнать задачи генерации превью и транскодирования для всех ассетов.
Автоматизация: скрипт с отправкой в S3
Ниже — рабочий сценарий для cron или systemd-таймера. Он снимает дамп базы, затем отправляет и библиотеку, и дампы в S3-хранилище через Restic: инструмент шифрует данные на клиенте и дедуплицирует их, поэтому повторные копии фототеки занимают минимум места. Пароль репозитория и ключи доступа хранятся в файлах с правами 600, а не в командной строке.
# /etc/restic/immich.env — права 600, владелец root
# export AWS_ACCESS_KEY_ID="..."
# export AWS_SECRET_ACCESS_KEY="..."
# export RESTIC_REPOSITORY="s3:https://s3.storage.selcloud.ru/immich-backup"
# export RESTIC_PASSWORD_FILE="/etc/restic/immich.pass"
#!/usr/bin/env bash
# /usr/local/bin/immich-backup.sh — резервное копирование Immich
set -euo pipefail
UPLOAD_LOCATION="/mnt/immich-data/library"
DUMP_DIR="/mnt/immich-data/manual-dumps"
DB_NAME="immich"
DB_USER="postgres"
DUMP_RETENTION_DAYS=7
# Секреты и параметры репозитория Restic
source /etc/restic/immich.env
install -d -m 750 "$DUMP_DIR"
STAMP="$(date +%F_%H-%M)"
# Шаг 1: база данных — строго первой
docker exec -t immich_postgres pg_dump --clean --if-exists \
--dbname="$DB_NAME" --username="$DB_USER" | gzip > "$DUMP_DIR/db_${STAMP}.sql.gz"
# Шаг 2: файловая часть и дампы уезжают в S3
# Для экономии места regenerable-каталоги можно исключить:
# --exclude "$UPLOAD_LOCATION/thumbs" --exclude "$UPLOAD_LOCATION/encoded-video"
restic backup "$UPLOAD_LOCATION" "$DUMP_DIR" --tag immich
# Шаг 3: политика хранения — 7 дней, 4 недели, 3 месяца
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 3 --prune
# Шаг 4: локальная ротация дампов
find "$DUMP_DIR" -type f -name '*.sql.gz' -mtime +"$DUMP_RETENTION_DAYS" -delete
echo "Immich backup completed: $STAMP"
# Права на исполнение и запуск по расписанию (пример: ежедневно в 03:30)
sudo chmod 750 /usr/local/bin/immich-backup.sh
echo '30 3 * * * root /usr/local/bin/immich-backup.sh >> /var/log/immich-backup.log 2>&1' | sudo tee /etc/cron.d/immich-backup
Готовые шаблоны, которые пригодятся при развитии этой схемы: скрипт бэкапа PostgreSQL в S3 для отдельного сервера СУБД, конфигурация Rclone для S3 и облачных дисков — если удобнее синхронизировать каталоги, и скрипт умной ротации бэкапов с правилом «7 дней / 4 недели / 3 месяца». Общая логика шифрованных копий контейнеров разобрана в статье автоматические шифрованные бэкапы VDS и Docker на S3.
Восстановление: два пути и одна ловушка
Восстановление из веб-интерфейса — рекомендуемый способ: в Administration → Maintenance откройте раздел восстановления базы, выберите нужный дамп и подтвердите. Система сама создаст точку отката перед операцией, а при сбое вернётся к ней. Учтите: восстановление полностью заменяет текущую базу выбранной копией.
Восстановление из командной строки требует чистой установки: Immich не должен запускаться ни разу с момента создания контейнеров, иначе Postgres выдаст конфликты вида «relation already exists» или нарушение внешних ключей. Если сервер уже стартовал, каталог базы DB_DATA_LOCATION придётся очистить. В ряде сценариев помогает документированная переменная DB_SKIP_MIGRATIONS=true — она запрещает серверу запускать миграции, пока вы восстанавливаете базу, но после восстановления её нужно убрать и перезапустить сервисы.
💡 Совет инженера
Раз в квартал проверяйте не наличие бэкапа, а факт восстановления: разверните Immich на тестовой виртуалке, восстановите дамп и файлы, затем сверьте целостность хранилища на странице Administration → System Integrity. Для Restic полезна команда restic check --read-data-subset=5% — она выборочно перечитывает данные из репозитория и ловит повреждения, которые не видны при обычном restic snapshots.
8. Машинное обучение на CPU, тюнинг базы и безопасность периметра
Самый прожорливый сервис в Immich — контейнер машинного обучения. Он выполняет умный поиск по содержимому кадра, распознавание лиц и OCR-обработку текста на снимках. На обычном VDS графического ускорителя нет, поэтому все модели работают на CPU, и настройка сводится к аккуратному управлению потоками и временем жизни моделей в памяти.
| Переменная | По умолчанию | Зачем менять |
|---|---|---|
MACHINE_LEARNING_MODEL_TTL |
300 с | Время простоя до выгрузки модели из памяти; значение ≤ 0 отключает выгрузку |
MACHINE_LEARNING_REQUEST_THREADS |
число ядер CPU | Первое, что рекомендуют настраивать при подборе параллелизма |
MACHINE_LEARNING_MODEL_INTER_OP_THREADS |
1 | Число параллельных операций модели |
MACHINE_LEARNING_MODEL_INTRA_OP_THREADS |
2 | Потоки внутри одной операции |
MACHINE_LEARNING_WORKERS |
1 | Каждый процесс дублирует модели в памяти — увеличивать не рекомендуется |
MACHINE_LEARNING_MODEL_ARENA |
true | Предварительный резерв памяти против фрагментации; на грани по RAM стоит отключить |
Отдельная тонкость — предзагрузка моделей. Текстовая CLIP-модель обязательна для умного поиска, остальные подгружаются по мере обращения. Документация предупреждает: первые поиски могут быть медленными, потому что фоновые задачи вызывают загрузку дополнительных моделей в память и блокируют остальные запросы до завершения. Если памяти достаточно, модели можно предзагрузить переменными MACHINE_LEARNING_PRELOAD__CLIP__VISUAL, MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION и аналогичными; если памяти в обрез — наоборот, лучше не форсировать загрузку.
⚠️ Важное предупреждение: режим 4 ГБ и отключение ML
Официальная документация допускает работу Immich на 4 ГБ памяти, но только с отключёнными функциями машинного обучения. Практически это означает, что контейнер immich-machine-learning не запускается: загрузка и хранение фото, альбомы, мобильный бэкап и общий доступ работают, а умный поиск по содержимому, распознавание лиц и OCR — нет. Если вы уже поднимали ML-контейнер, разумный компромисс — уменьшить время жизни моделей через MACHINE_LEARNING_MODEL_TTL.
Аппаратное ускорение на VDS, как правило, недоступно: официальный манифест предлагает отдельные варианты образов и файлы hwaccel.transcoding.yml / hwaccel.ml.yml для NVIDIA (CUDA/NVENC), Intel QuickSync, VAAPI, ROCm и ARM-NN. Все они требуют проброса реального устройства в контейнер, чего на типовой виртуалке нет. Не тратьте время на попытки включить аппаратный транскодинг «на всякий случай» — на CPU-инстансе это не заработает.
База данных: что действительно нужно настраивать
С версии v3 официальный образ ghcr.io/immich-app/postgres уже содержит внутри себя healthcheck и дополнительный тюнинг — разработчики прямо пишут об этом в разделе обновлений. Поэтому не нужно вручную дописывать параметры Postgres в манифест: достаточно указать тип накопителя.
environment:
# SSD (значение по умолчанию) или HDD
DB_STORAGE_TYPE: 'SSD'
Эта переменная выбирает один из двух профилей конфигурации — postgresql.ssd.conf или postgresql.hdd.conf, которые управляют в первую очередь параметром effective_io_concurrency. Для SSD выставляется конкурентный ввод-вывод, для HDD — последовательный. Практический вывод для VDS: держите базу на NVMe и оставляйте значение SSD; если база оказалась на сетевом диске или HDD, производительность просядет, а сетевые хранилища под DB_DATA_LOCATION официально не поддерживаются вовсе.
Если вы используете внешний, отдельный сервер Postgres (сценарий standalone), настройки нужно считать самостоятельно — готовые профили для 8 ГБ собраны в конфигураторе тюнинг PostgreSQL под 8 ГБ RAM. Подчеркну: для штатного docker-развёртывания Immich это не требуется, образ уже настроен.
Безопасность: четыре шага после установки
Первый шаг — закрыть регистрацию. Сразу после создания администратора задайте в .env переменную IMMICH_ALLOW_SETUP=false и пересоздайте контейнеры. Она отключает публичные эндпоинты /auth/admin-sign-up и /admin/database-backups/start-restore: никто со стороны не сможет создать учётную запись или запустить восстановление базы. Помните, что для восстановления из веб-интерфейса переменную придётся временно вернуть в true.
Второй шаг — вынести пароль базы из файла окружения. Immich поддерживает чтение секретов из файлов: переменные DB_PASSWORD_FILE и REDIS_PASSWORD_FILE, а также каталог CREDENTIALS_DIRECTORY для Docker Secrets и systemd-credentials. Это позволяет держать ключ в файле с правами 600 и не хранить его открытым текстом в .env.
Третий шаг — контроль периметра. Порт 2283 привязан к 127.0.0.1, наружу смотрят только 80/443, либо не смотрит ничего при доступе через оверлейную сеть. SSH-доступ закрывается по ключам, а от брутфорса ставятся Fail2ban или CrowdSec — методики разобраны в статьях про настройку Fail2ban на Ubuntu 24.04 и защиту VDS через CrowdSec в Docker Compose.
Четвёртый шаг — не «плыть» по версиям. Зафиксированная версия в IMMICH_VERSION и обновление только после чтения release notes — это не перфекционизм, а защита от ситуации, когда ночное автообновление приносит breaking change и сервер, обслуживающий семейный архив, не поднимается.
9. Чек-лист администратора и типичные ошибки
Собираем всё воедино. Ниже — восемь сбоев, которые встречаются чаще всего при развертывании Immich на виртуальном сервере, и способы их устранения.
| Симптом | Причина | Решение |
|---|---|---|
unknown shorthand flag: 'd' |
Установлен пакет docker.io из репозитория Ubuntu без актуального плагина Compose |
Переустановить Docker из официального репозитория разработчика |
require Docker Engine v25 or later |
Слишком старый Docker Engine для параметра healthcheck | Обновить Engine до 25+ либо убрать start_interval из манифеста |
Illegal instruction в логах ML |
Процессор виртуальной машины ниже уровня x86-64-v2 | Сменить тип процессора в панели провайдера или площадку |
| Postgres перезапускается, фото не открываются | Сработал OOM Killer из-за отсутствия лимитов или нехватки RAM базе | Задать mem_limit, выделить базе от 2 ГБ, добавить Swap |
| Мобильное приложение не подключается | Сервис опубликован на подпути (/immich) или неверно проброшены заголовки прокси |
Публиковать в корне домена, пробрасывать X-Forwarded-* |
| После восстановления часть ассетов «битая» | Копии базы и файлов снимались в неверном порядке или не полностью | База — первой, файлы — второй; сверять состояние в System Integrity |
| Файлы на диске есть, в интерфейсе их нет | Файлы скопированы в каталоги библиотеки вручную | Загружать через интерфейс или подключать как External Library |
Изменения в .env не применяются |
Переменные окружения читаются только при создании контейнера | Пересоздать сервисы: docker compose up -d --force-recreate |
📋 Чек-лист перед публикацией фотоархива
- Сервер на KVM, от 8 ГБ RAM, процессор с поддержкой x86-64-v2, NVMe-диски.
- Docker Engine 25+ и плагин Compose v2 установлены из официального репозитория.
UPLOAD_LOCATION— на отдельном диске,DB_DATA_LOCATION— на локальном NVMe.- Порт
2283опубликован только на127.0.0.1; наружу смотрят 80/443 либо ничего. - Каждому сервису заданы
mem_limitиcpus, базе выделено не менее 2 ГБ. - Создан Swap 4 ГБ и задан
vm.swappiness=10. - Версия зафиксирована в
IMMICH_VERSION, обновления — только после чтения release notes. - Автоматические дампы базы включены, офсайт-копия снимается Restic, восстановление проверено.
- После регистрации администратора установлено
IMMICH_ALLOW_SETUP=false. - Проверены внешние порты и срок SSL-сертификата, настроены алерты мониторинга.
Immich v3 — редкий пример self-hosted проекта, который действительно закрывает потребительский сценарий, а не требует постоянного администрирования. Плата за это — дисциплина в трёх вещах: память (не меньше 6, лучше 8 ГБ), хранилище (база только на локальном SSD) и копии (дамп базы плюс файлы, с регулярной проверкой восстановления). Если эти три условия выполнены, сервер quietly работает годами, а фототека принадлежит вам, а не чужому аккаунту.
Для самопроверки после развертывания используйте бесплатные утилиты SysKit: сканирование портов подтвердит, что 2283 закрыт снаружи, проверка SSL покажет срок сертификата, DNS lookup — актуальность A-записи, а определение IP пригодится при настройке белых списков и VPN.
Разверните фотоархив на собственном сервере
Для Immich нужен сервер с 8 ГБ RAM, четырьмя ядрами и быстрым NVMe-диском — именно такой запас обеспечивает комфортную индексацию библиотеки и работу машинного обучения без OOM Killer.
Перейти и выбрать конфигурацию