Homepage на сервере: частые ошибки и решения
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
Два рабочих решения:
- Прокинуть GID в контейнер. Добавьте в
docker-compose.ymlсекциюgroup_addс этим числом:
group_add:
- "999"
- 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 ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →