MAATRIX / Блог / Heimdall на сервере: частые ошибки и решения

Heimdall на сервере: частые ошибки и решения

MAATRIX

Heimdall (linuxserver/heimdall) — простой стартовый дашборд для домашней лаборатории или небольшого сервера: сетка плиток-приложений, немного статусной логики и почти нулевой порог входа в отличие от YAML-монстров вроде Homepage. Но именно из-за этой простоты новичков подстерегают свои грабли — приложения бесследно исчезают после пересоздания контейнера, база данных вдруг оказывается заблокирована, а за реверс-прокси вместо дашборда выскакивает страница с кодом 419. Разберём каждую проблему по схеме «симптом — причина — решение».

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

После пересоздания контейнера пропали все приложения

Самая обидная ошибка новичков. Контейнер пересоздали (обновили образ, поменяли переменную окружения) — а на дашборде пусто, будто Heimdall только что установили. Причина почти всегда одна: том /config либо не был примонтирован вообще, либо смонтирован неправильно, и вся конфигурация жила только внутри слоя контейнера, который docker compose up -d --force-recreate или docker rm уничтожает без следа.

Heimdall хранит абсолютно всё состояние — список приложений, категорий, тегов, настройки темы — в одном SQLite-файле внутри /config:

config/
├── www/
│   └── heimdall.sqlite   # вся база: приложения, категории, теги, настройки
├── keys/                  # ключи шифрования Laravel (APP_KEY)
├── log/
└── ssl/                   # сертификаты, если включён встроенный HTTPS

Рабочий docker-compose.yml:

services:
  heimdall:
    image: lscr.io/linuxserver/heimdall:latest
    container_name: heimdall
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/Moscow
    volumes:
      - ./config:/config
    ports:
      - "8080:80"
      - "8443:443"
    restart: unless-stopped

Если строки volumes не было или путь указывал на анонимный том без явного пути на хосте, при пересоздании контейнера Docker создаёт новый пустой volume — данные теряются безвозвратно. Проверить, что том реально смонтирован туда, куда вы думаете:

docker inspect heimdall --format '{{ range .Mounts }}{{ .Source }} -> {{ .Destination }}{{ "\n" }}{{ end }}'

Если в выводе /config указывает не на ./config в проекте, а на путь вида /var/lib/docker/volumes/heimdall_config/_data — это тоже нормально (именованный volume), но бэкапить нужно именно его. Backup сводится к одной команде — остановить контейнер и скопировать файл базы, чтобы не поймать её в момент записи:

docker compose stop heimdall
cp -a ./config/www/heimdall.sqlite ./heimdall-backup-$(date +%F).sqlite
docker compose start heimdall

Permission denied и permissions-инициализация при первом запуске

Образ Heimdall собран на базе linuxserver.io, поэтому права внутри контейнера регулируются переменными PUID и PGID, а не UID/GID, зашитым в образ. Если их не задать, при первом старте контейнер инициализирует /config от root, а на следующем запуске — уже от другого пользователя, и в логах появится Permission denied при попытке Laravel-процесса писать в /config/log или /config/www/heimdall.sqlite.

Узнать, какой UID/GID стоит указать:

id $(whoami)

Задать в docker-compose.yml:

    environment:
      - PUID=1000
      - PGID=1000

После смены PUID/PGID на уже существующем /config контейнер сам не переподхватит владельца файлов — нужно поправить права вручную и пересоздать контейнер:

sudo chown -R 1000:1000 ./config
docker compose up -d --force-recreate heimdall

Отдельная ловушка — запуск на хосте с SELinux (обычно это не Ubuntu/Debian, а RHEL-подобные дистрибутивы). Там даже при верных PUID/PGID запись в volume блокируется политикой безопасности, а не правами Unix, и в journalctl/audit.log рядом с ошибкой видны записи avc: denied. Решение — добавить суффикс :z к volume в compose-файле, чтобы Docker сам проставил нужный SELinux-контекст:

    volumes:
      - ./config:/config:z

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

SQLite "database is locked" при высокой нагрузке

Heimdall хранит состояние не в PostgreSQL или MySQL, а в файле SQLite — осознанный компромисс ради простоты установки, но у него есть цена: SQLite допускает только одного писателя в момент времени. Если дашборд открыт в нескольких вкладках одновременно и кто-то параллельно редактирует приложения, в логах контейнера иногда всплывает:

docker logs heimdall 2>&1 | grep -i "database is locked"

На небольшом домашнем дашборде с одним активным пользователем это скорее теоретический риск. Ситуация обостряется на сетевых файловых системах (NFS, SMB-шары, синхронизируемые каталоги вроде Dropbox или Google Drive), которые Heimdall хранит как /config: блокировки файлов там работают ненадёжно, и «database is locked» может появляться даже при одном пользователе. Правило простое — /config должен лежать на локальном диске сервера, а не на сетевом или синхронизируемом хранилище. Синхронизировать между серверами стоит не саму базу, а регулярные бэкапы (см. раздел выше).

Если файл базы повреждён после сбоя (внезапное отключение питания, kill -9 контейнера в момент записи), проверить целостность можно так — если на хосте установлен пакет sqlite3:

sqlite3 ./config/www/heimdall.sqlite "PRAGMA integrity_check;"

Ответ ok значит, что с файлом всё в порядке и причина ошибки в другом (например, в том же сетевом хранилище). Любой другой ответ — повод восстановить базу из последнего бэкапа, а не пытаться чинить файл вручную.

"419 Page Expired" или бесконечный редирект за реверс-прокси

Heimdall написан на Laravel, а у Laravel из коробки включена CSRF-защита форм — при сохранении приложения или настройки браузер отправляет POST-запрос с токеном, и если сервер видит несовпадение (или не видит куки вовсе), в ответ прилетает страница 419 Page Expired. За реверс-прокси (Traefik, nginx, Nginx Proxy Manager) это происходит чаще, чем при прямом доступе, по двум причинам.

Первая — прокси не передаёт заголовки X-Forwarded-Proto и X-Forwarded-Host, и Laravel внутри контейнера не понимает, что запрос пришёл по HTTPS через внешний домен, а не по HTTP на внутренний порт. Для nginx как прокси перед контейнером нужны как минимум:

location / {
    proxy_pass http://127.0.0.1:8080;
    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;
}

Если прокси настроен через Traefik, эти заголовки он добавляет автоматически — тогда причину стоит искать в другом месте.

Вторая причина — куки сессии помечены как Secure, а сайт при этом открывается по HTTP хотя бы на одном из адресов (локально по IP без SSL, а через домен — с SSL терминацией на прокси). Браузер отказывается отправлять Secure-куку по незащищённому соединению, сессия не сохраняется, и любая форма падает с 419. Практическое правило — определитесь с одним способом доступа и не переключайтесь между ним и другим в рамках одной сессии браузера.

Если ошибка появляется у всех пользователей постоянно, а не эпизодически — проверьте права на /config/keys: там лежит ключ шифрования Laravel, и если контейнер не может его прочитать (см. раздел про PUID/PGID), сессии не создаются вовсе.

Enhanced-плитки не показывают статус приложения

У некоторых плиток Heimdall есть режим "enhanced" — маленький индикатор online/offline и иногда дополнительные данные прямо на карточке приложения. В отличие от Homepage, где виджет опрашивает API сервиса на сервере (со стороны контейнера), у Heimdall статус-проверка enhanced-плиток по умолчанию выполняется прямо из браузера пользователя — JavaScript на странице дашборда сам обращается к адресу приложения.

Отсюда два типичных симптома и разные причины:

  • Индикатор постоянно красный (offline), хотя сервис работает. Если Heimdall открыт по HTTPS, а адрес приложения в поле URL указан по HTTP — браузер блокирует такой запрос как mixed content, это видно в консоли разработчика (F12 → Console) как Blocked loading mixed active content. Решение — либо открывать приложение тоже по HTTPS (даже с самоподписанным сертификатом и исключением в браузере), либо использовать статическую плитку без enhanced-режима.
  • В консоли браузера ошибка CORS. Проверка статуса — кросс-доменный запрос из браузера к другому сервису, а не серверный запрос контейнера. Если приложение не отдаёт заголовок Access-Control-Allow-Origin, разрешающий обращение с адреса дашборда, браузер молча блокирует ответ, даже если сам сервис отвечает нормально. У части приложений CORS настраивается их конфигом, у части — нет, и тогда практичный выход один — обычная плитка-ссылка без enhanced-статуса.

Быстрая диагностика — вкладка Network в инструментах разработчика: видно, какой запрос падает и с каким кодом ошибки, это быстрее, чем перебирать варианты конфига вслепую.

Иконки приложений не грузятся или подгружаются не той версии

Heimdall при добавлении приложения пытается автоматически подобрать иконку по названию из встроенной библиотеки и предлагает поиск логотипа через внешние источники прямо в форме. Если сервер не может достучаться до интернета (закрытый файрвол, приватная сеть без исходящего доступа), поиск иконок просто не находит результатов, а само добавление приложения без иконки проходит нормально.

Проверить исходящий доступ в интернет с сервера:

curl -sI https://www.google.com | head -1

Если внешнего доступа нет и не будет по политике сети — загружайте иконки вручную через ту же форму (Heimdall сохранит файл в /config), не полагаясь на автопоиск.

Отдельная жалоба — после смены логотипа на дашборде продолжает отображаться старая иконка. Это почти всегда кеш браузера: Heimdall не всегда меняет имя файла при замене картинки, и браузер отдаёт закешированную версию по старому URL. Жёсткая перезагрузка страницы (Ctrl+Shift+R) решает вопрос за секунду.

Обновление образа сломало кастомную тему или CSS

Если на дашборде подключён кастомный CSS через настройки темы, после обновления образа lscr.io/linuxserver/heimdall:latest иногда часть стилей перестаёт применяться — новая версия меняет разметку страницы (имена классов, структуру блоков), а кастомный CSS написан под старые селекторы. Формально это не баг конфигурации, а следствие того, что Heimdall не гарантирует обратную совместимость вёрстки между версиями.

Практический подход — не гнаться за тегом latest на проде, зафиксировать версию образа явно:

    image: lscr.io/linuxserver/heimdall:2.7.4

и обновлять осознанно, проверяя changelog проекта. Перед любым обновлением образа сделайте бэкап /config (см. первый раздел) — откат к предыдущему тегу без отката базы иногда оставляет дашборд в промежуточном состоянии, если миграция схемы базы между версиями уже применилась.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Чем Heimdall принципиально отличается от Homepage?

Архитектурой. Heimdall — PHP/Laravel-приложение с SQLite-базой и графическим редактором в интерфейсе, без единого YAML-файла. Homepage — Node.js-приложение с YAML-конфигом и более развитой системой server-side виджетов. Heimdall проще завести за пять минут, Homepage — гибче для десятков интегрированных сервисов со статистикой. Специфичные грабли Homepage разобраны в отдельной статье про его частые ошибки.

Можно ли перенести приложения Heimdall на новый сервер без переустановки?

Да, проще всего — скопировать каталог /config целиком (включая www/heimdall.sqlite, keys и ssl) и указать его в volume нового контейнера с тем же PUID/PGID. Копировать только файл базы без keys не стоит — без совпадающего ключа шифрования часть данных может не открыться корректно.

Нужен ли Heimdall встроенный HTTPS (порт 443 контейнера), если сервер и так за Traefik или nginx?

Как правило нет. Если SSL уже терминируется на прокси перед контейнером, достаточно пробросить только HTTP-порт (80) контейнера, а порт 443 самого Heimdall не публиковать — это упрощает конфиг и снимает вопросы с сертификатами внутри контейнера.

После сбоя сервера дашборд открывается, но все приложения — как при первой установке. Это точно потеря данных?

В подавляющем большинстве случаев да — контейнер создал заново пустой /config, не найдя смонтированный volume по ожидаемому пути (см. первый раздел). Без бэкапа heimdall.sqlite восстановить список приложений можно только вручную.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →