Homepage dashboard¶
Overview¶
Homepage is a static, YAML-configured start page for a homelab: tiles with links, icons and descriptions, grouped by category, plus a few info widgets (search, date/time, weather). It can also show live per-service stats through API widgets and running/healthy dots through the Docker API.
I keep mine deliberately simple: links, icons and descriptions, no per-service API widgets. It loads instantly, and it doesn't need an API key for every service. The status dots come from Docker for containers on the same host.
Compose¶
services:
homepage:
image: ghcr.io/gethomepage/homepage:v2.6.0
container_name: homepage
restart: unless-stopped
env_file:
- .env
ports:
- "3010:3000"
volumes:
# Config comes from this directory itself (git-managed, next to compose.yaml)
- ./:/app/config
- /var/run/docker.sock:/var/run/docker.sock:ro
# Every host:port you open the dashboard by (comma-separated, no spaces) - see the gotcha below
HOMEPAGE_ALLOWED_HOSTS=home.<YOUR_LOCAL_DOMAIN>,<YOUR_SERVER_IP>:3010,<YOUR_HOSTNAME>:3010
Config straight from the repo¶
The volume ./:/app/config mounts the stack's own directory from git: services.yaml, settings.yaml,
widgets.yaml, bookmarks.yaml, docker.yaml, custom.css, custom.js all sit next to compose.yaml. Editing a
tile is a commit and a push; the GitOps redeploy recreates the container and the dashboard updates. No SSH, no
editing files on the host.
This is safe only because Homepage never writes to its config: it has no in-app editor. Don't copy the pattern for
apps that save settings or state into their config directory at runtime; those need a stable host path (e.g.
/mnt/appdata/<service>), or every deploy fights the app.
The HOMEPAGE_ALLOWED_HOSTS gotcha¶
Since v1.0, Homepage only answers requests whose Host header is in HOMEPAGE_ALLOWED_HOSTS (localhost:3000 and
127.0.0.1:3000 are always allowed). It protects the API proxy against DNS-rebinding style attacks. Anything else gets
a host validation error instead of the dashboard.
The catch: it matches host:port exactly as the request sends it. So
home.<YOUR_LOCAL_DOMAIN>(through the reverse proxy on 443) is one entry,<YOUR_SERVER_IP>:3010and<YOUR_HOSTNAME>:3010(direct to the published port) are separate entries, and- an uptime monitor that checks
http://<YOUR_HOSTNAME>:3010needs its address in the list too, or the monitor goes red while the dashboard works fine in your browser.
That last one bit me: the monitor sat at DOWN for days because it used the direct host:port and only the proxy
name was allowed. When you see host validation errors, check the container log, which prints the host exactly as
received, and add that string. * disables the check; don't do that on anything reachable beyond your LAN.
Configuration notes¶
settings.yaml:layout:setsstyle: rowand a column count per category, tuned to the number of tiles so rows fill evenly.hideVersion,useEqualHeights, andquicklaunch(type on the page to search tiles) are worth turning on.services.yaml: each tile isicon,href,description, and optionallycontainer: <name>for the status dot. Prefer the reverse-proxy hostname forhref; fall back tohost:portonly where there's no proxy entry. Re-check the links now and then againstdocker ps: a dashboard full of dead tiles is worse than none.docker.yaml:local: { socket: /var/run/docker.sock }gives dots for containers on the same host. For other hosts, use a docker-socket-proxy on each one that only allows read-only container endpoints, and point adocker.yamlentry at it. Don't publish a raw Docker socket over TCP. Even a:rosocket mount exposes the full read API (env vars included), so mount it only into containers you trust.widgets.yaml: search, date/time, Open-Meteo weather. I dropped the host resources widget because it only shows the host Homepage runs on, which is misleading on a fleet dashboard; per-host stats live in Beszel.custom.css/custom.js: loaded automatically from the config directory. Homepage is a client-rendered app, so custom JS has to wait for (and survive re-renders of) the elements it touches; poll and fail silently.- Pin the image. Tag pinned, Renovate opens the update PRs.