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

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

MAATRIX

Homepage (gethomepage/homepage) — самый популярный сейчас стартовый дашборд для домашнего или небольшого прод-сервера: один YAML-конфиг, виджеты статуса сервисов, интеграция с Docker и десятками приложений вроде Sonarr, Pi-hole или Portainer. Но именно из-за обилия интеграций с ним чаще всего спотыкаются на трёх вещах: виджет не тянет данные, Docker-сокет отдаёт отказ в доступе, а за реверс-прокси сервер вместо дашборда показывает ошибку хоста. Разберём каждую по схеме «симптом — причина — решение».

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

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

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

Структура конфигов и типичные ошибки YAML

Homepage хранит всё в каталоге config, который монтируется в контейнер как volume. Файлов там несколько, и каждый отвечает за своё:

config/
├── settings.yaml    # общие настройки: тема, layout, provider для виджетов
├── services.yaml     # список сервисов и их виджеты
├── bookmarks.yaml    # закладки
├── widgets.yaml      # info-виджеты (погода, поиск, ресурсы сервера)
├── docker.yaml        # подключения к Docker-хостам
└── custom.css / custom.js

Минимальный docker-compose.yml для самого Homepage:

services:
  homepage:
    image: ghcr.io/gethomepage/homepage:latest
    container_name: homepage
    ports:
      - "3000:3000"
    volumes:
      - ./config:/app/config
      - /var/run/docker.sock:/var/run/docker.sock:ro
    restart: unless-stopped

Главный источник ошибок на старте — не docker-compose, а именно отступы в YAML. Homepage при поломанном файле не всегда падает целиком: часто просто пропадает один блок сервиса или виджета, а в логах контейнера остаётся невзрачная строка вида YAMLException с номером строки:

docker compose logs -f homepage

Три частые ловушки в services.yaml:

  • Табы вместо пробелов. YAML не терпит табуляцию для отступов — редактор мог подставить её автоматически. Проверьте файл через cat -A config/services.yaml | grep '\^I' — символ ^I и есть таб.
  • Группы сервисов на неправильном уровне. Структура — список групп, внутри каждой группы список сервисов, внутри каждого сервиса — словарь с полями. Один лишний или недостающий отступ ломает всю ветку ниже.
  • Двоеточие внутри значения без кавычек. Например href: http://192.168.1.10:8080 парсится нормально, а вот произвольная строка с : посередине без кавычек иногда трактуется YAML-парсером как новая пара ключ-значение. Оборачивайте такие строки в кавычки.

Если после правки файл всё ещё не подхватывается — перечитайте конфиг принудительно, перезапустив контейнер: обычно Homepage подхватывает изменения на лету, но иногда файловый watcher в Docker на некоторых файловых системах (особенно сетевых share) не срабатывает, и помогает docker compose restart homepage.

Виджет сервиса показывает "Widget error" вместо данных

Симптом: карточка сервиса на дашборде есть, ссылка (href) открывается нормально, но вместо цифр — красная плашка Widget error или бесконечный спиннер. Это значит, что Homepage-сервер (не браузер) не смог достучаться до API самого сервиса — Homepage делает запросы виджетов server-side, а не из браузера пользователя.

Первое, что проверяют — URL в поле url виджета. Частая ошибка: указывают адрес, по которому сервис доступен из браузера (https://sonarr.example.com), а не тот, по которому его видит контейнер Homepage внутри Docker-сети. Если Sonarr и Homepage — соседние контейнеры в одном compose-проекте, обращаться нужно по имени сервиса и внутреннему порту:

- Sonarr:
    icon: sonarr.png
    href: https://sonarr.example.com
    widget:
      type: sonarr
      url: http://sonarr:8989
      key: "{{HOMEPAGE_VAR_SONARR_KEY}}"

Обратите внимание: href (куда ведёт клик) и url виджета (откуда тянутся данные) — разные адреса и решают разные задачи. Первый должен быть доступен браузеру пользователя, второй — контейнеру Homepage.

Вторая частая причина — неверный или просроченный API-ключ. У большинства виджетов (Sonarr, Radarr, Proxmox, качественные self-hosted панели) свой формат ключа: где-то это key, где-то username/password, где-то token. Синтаксис у каждого типа виджета свой — сверяйтесь с официальным списком виджетов Homepage, не переносите поля одного виджета на другой по аналогии, это одна из самых частых причин Widget error.

Третья причина — сервис и Homepage в разных Docker-сетях. Даже если оба контейнера подняты и работают, обращение по имени сервиса сработает, только если они в одной user-defined сети. Проверить:

docker network inspect <имя_сети> | grep -A3 Name

Если сети разные — либо подключите оба контейнера к общей сети, либо обращайтесь по IP хоста и опубликованному порту (http://host.docker.internal:8989 на Docker Desktop или IP хоста в Linux).

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

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

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

Docker-виджет: permission denied на docker.sock

Отдельная категория ошибок — интеграция с самим Docker: список контейнеров, их статус и статистика прямо на дашборде. Настраивается через config/docker.yaml:

my-docker:
  socket: /var/run/docker.sock

Если в логах контейнера видно permission denied при обращении к сокету — дело в правах. docker.sock на хосте обычно принадлежит группе docker (GID часто 999, но может отличаться), а процесс внутри контейнера Homepage запускается от отдельного непривилегированного пользователя, у которого этой группы нет.

Проверьте реальный GID группы docker на хосте:

getent group docker

Два рабочих решения:

  1. Прокинуть GID в контейнер. Добавьте в docker-compose.yml секцию group_add с этим числом:
    group_add:
      - "999"
  1. Docker Socket Proxy (рекомендуемый вариант для прод-сервера). Прямой доступ к docker.sock фактически равен root на хосте — для дашборда, который может быть виден и другим пользователям, это избыточный риск. Безопаснее поставить прослойку tecnativa/docker-socket-proxy, которая отдаёт только read-only доступ к нужным эндпоинтам:
  docker-socket-proxy:
    image: tecnativa/docker-socket-proxy
    environment:
      CONTAINERS: 1
      POST: 0
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    restart: unless-stopped

И в docker.yaml указать хост прокси вместо сокета:

my-docker:
  host: docker-socket-proxy
  port: 2375

Второй вариант чуть дольше настраивается, но избавляет от пляски с GID на каждом новом сервере и не отдаёт контейнеру Homepage полный контроль над Docker-демоном.

Иконки сервисов не грузятся

Мелкая, но частая жалоба: карточки сервисов есть, а вместо иконок — серые квадраты или битые картинки. По умолчанию Homepage тянет иконки по имени (icon: sonarr.png) из внешнего CDN-репозитория с набором логотипов популярных приложений. Если сервер не может достучаться до этого CDN — из-за файрвола, отсутствия исходящего интернета или блокировки на уровне DNS, — иконки просто не подгружаются, при этом остальной дашборд продолжает работать нормально.

Проверить доступность CDN с сервера:

curl -sI https://cdn.jsdelivr.net | head -1

Если ответ не 2xx/3xx — проблема на сетевом уровне, а не в конфиге Homepage. Варианты решения:

  • Использовать иконки набора Material Design Icons без обращения к внешнему CDN изображений: icon: mdi-server — они рендерятся шрифтом, а не картинкой.
  • Положить свои PNG/SVG в config/icons/ и указывать icon: /icons/myicon.png — тогда файл отдаёт сам Homepage, без внешних запросов.
  • Указать прямую ссылку на иконку в поле icon (полный https:// URL) — подходит, если внешний CDN недоступен именно для конкретного набора логотипов, но общий доступ в интернет у сервера есть.

"Invalid Host header" за Traefik или nginx

Частный, но неприятный случай: локально по IP и порту 3000 дашборд открывается нормально, а через домен за реверс-прокси браузер получает голую страницу с текстом Invalid Host header. Причина в том, что Homepage построен на Next.js, а Next.js по умолчанию проверяет заголовок Host входящего запроса и отклоняет всё, что не совпадает с ожидаемым.

Решение — явно перечислить разрешённые хосты переменной окружения HOMEPAGE_ALLOWED_HOSTS:

    environment:
      HOMEPAGE_ALLOWED_HOSTS: home.example.com

Если дашборд должен открываться и по внутреннему IP, и по домену — перечислите оба значения через запятую, без пробелов:

      HOMEPAGE_ALLOWED_HOSTS: home.example.com,192.168.1.50:3000

После смены переменных окружения контейнер обязательно нужно пересоздать, а не просто перезапустить — restart не подхватывает изменения environment из compose-файла:

docker compose up -d --force-recreate homepage

Если у вас уже настроен Traefik как reverse proxy перед другими сервисами, логика меток для Homepage ничем не отличается от любого другого веб-контейнера — а вот описанная выше ошибка Invalid Host header специфична именно для Next.js-приложений вроде Homepage. Если сам Traefik ведёт себя странно (не тот сертификат, 404 на все запросы) — это уже отдельная тема, разобранная в статье про частые ошибки Traefik.

Переменные {{HOMEPAGE_VAR_*}} не подставляются

Чтобы не хранить API-ключи прямо в YAML-файлах (которые удобно держать в git), Homepage поддерживает подстановку переменных окружения через синтаксис {{HOMEPAGE_VAR_ИМЯ}}. Частая ошибка — переменная объявлена без нужного префикса или не проброшена в контейнер, и тогда в интерфейсе вместо значения буквально показывается строка {{HOMEPAGE_VAR_SONARR_KEY}} — Homepage молча пропускает подстановку, если переменной с таким именем не существует.

Правила, которые часто нарушают:

  • Имя переменной обязательно должно начинаться с HOMEPAGE_VAR_ — просто SONARR_KEY в .env файле подхвачен не будет.
  • Переменную нужно объявить и в .env, и передать контейнеру — через env_file: .env в compose-файле, либо явно в блоке environment.
  • После добавления новой переменной контейнер нужно пересоздать, простого restart недостаточно — как и в случае с HOMEPAGE_ALLOWED_HOSTS выше.

Пример .env рядом с docker-compose.yml:

HOMEPAGE_VAR_SONARR_KEY=abcdef1234567890
HOMEPAGE_VAR_PIHOLE_PASSWORD=supersecret

И в compose-файле:

    env_file:
      - .env

Проверить, что переменная реально попала внутрь контейнера, можно так:

docker exec homepage env | grep HOMEPAGE_VAR

Если строки нет в выводе — проблема на уровне docker-compose (не тот файл, не тот путь, лишний пробел вокруг = в .env), а не в самом Homepage.

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

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

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

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

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

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

Homepage — это то же самое, что Heimdall или Dashy?

Нет, это разные проекты с похожей задачей. Homepage выделяется большим числом готовых виджетов с живыми данными (не просто ссылками) и YAML-конфигом вместо GUI-редактора, что удобнее версионировать в git, но требует аккуратности с отступами.

Нужен ли обязательно доступ к docker.sock, если просто нужны карточки-ссылки без статистики контейнеров?

Нет. Docker-интеграция нужна только для автообнаружения контейнеров и их живого статуса. Если устраивает статичный список сервисов с ручными ссылками в services.yaml, docker.yaml можно вообще не создавать и монтировать сокет не обязательно.

Почему после редактирования YAML на сервере дашборд не обновился?

В большинстве случаев Homepage подхватывает изменения файлов на лету без перезапуска. Если этого не произошло — проверьте, что файл сохранён именно в смонтированном ./config, а не рядом с ним по ошибке, и что нет синтаксической ошибки, из-за которой обновлённый конфиг просто не прошёл валидацию (см. раздел про YAML выше).

Можно ли поставить Homepage перед уже работающим Portainer и другими панелями без конфликтов портов?

Да, конфликтов не будет — Homepage не проксирует трафик к другим сервисам, а только показывает карточки-ссылки и опрашивает их API. Если Portainer у вас уже настроен и с ним свои сложности — есть отдельный разбор частых ошибок Portainer на сервере.

Стоит ли выносить Homepage на отдельный субдомен или держать на корневом домене сервера?

Практического ограничения нет ни в ту, ни в другую сторону — выбор зависит от того, что ещё крутится на домене. Если сервер уже настроен по типовой продакшен-схеме с docker-compose, логичнее повесить дашборд на отдельный поддомен вроде home.example.com, чтобы не пересекаться с основными сервисами.

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

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

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