Homepage — The Dashboard That Actually Gets Used
1. What Homepage Is & Why You Want One
Section titled “1. What Homepage Is & Why You Want One”Every homelab grows the same way: one docker-compose.yml turns into five, then twenty. Before long you have a page of bookmarks named “Sonarr (local)”, “Sonarr (remote)”, “that thing on port 8686”, and a text file somewhere with IP:port combinations you keep forgetting.
Homepage (by gethomepage) fixes this with:
| Feature | What it does |
|---|---|
| Services | Tiled links to your apps, grouped into sections you define |
| Widgets | Live status pulled from each app’s API — episodes downloading, VPN IP, disk usage, etc. |
| Bookmarks | A separate bookmark bar for external links (trackers, docs, registrar) |
| Docker integration | Pulls container state (running / stopped / unhealthy) from the Docker socket |
| YAML config | Everything lives in a handful of files. No clicks, no database, no migrations. |
The whole thing is one static-feeling page that loads in ~200ms. It sits at home.falseviking.uk and is the browser homepage on every device in the house.
2. Deploy
Section titled “2. Deploy”The container is tiny and mounts two things: a config directory and the Docker socket (read-only).
services: homepage: image: ghcr.io/gethomepage/homepage:latest container_name: homepage environment: HOMEPAGE_ALLOWED_HOSTS: ${HOMEPAGE_ALLOWED_HOSTS} PUID: ${PUID} PGID: ${PGID} # API keys for widgets — pulled from .env HOMEPAGE_VAR_PLEX_KEY: ${HOMEPAGE_VAR_PLEX_KEY} HOMEPAGE_VAR_SONARR_KEY: ${HOMEPAGE_VAR_SONARR_KEY} HOMEPAGE_VAR_RADARR_KEY: ${HOMEPAGE_VAR_RADARR_KEY} HOMEPAGE_VAR_QBIT_USER: ${HOMEPAGE_VAR_QBIT_USER} HOMEPAGE_VAR_QBIT_PASS: ${HOMEPAGE_VAR_QBIT_PASS} ports: - 3000:3000 volumes: - /mnt/nfs/docker/docker/homepage/config:/config - /var/run/docker.sock:/var/run/docker.sock:ro restart: unless-stopped networks: - proxy - mediastack
networks: proxy: external: true mediastack: name: mediastack_mediastack external: trueHOMEPAGE_ALLOWED_HOSTS=home.falseviking.uk,192.168.1.248:3000PUID=1027PGID=65536# Add the HOMEPAGE_VAR_* keys as you wire up widgetsA few things to note:
HOMEPAGE_ALLOWED_HOSTSis mandatory on recent versions. If you load Homepage through a reverse proxy hostname that isn’t in this list, you’ll get a blank page. Include both the public domain and the LAN IP:port.- The Docker socket is mounted read-only. Homepage only needs to read container state.
- Homepage needs to be on the same Docker networks as the apps it talks to. On this host it’s on both
proxy(for Authentik, Grafana, etc.) andmediastack(for Sonarr, Radarr, qBit via gluetun).
Start it:
cd /opt/stacks/homepagedocker compose up -dFirst load will render an empty dashboard. That’s expected — the config is next.
3. The Config Files
Section titled “3. The Config Files”Everything is in the /config volume. On first boot, Homepage seeds these files with examples:
| File | Purpose |
|---|---|
settings.yaml | Global: title, theme, background image, column layouts |
services.yaml | The main tiles. Grouped into sections. Widgets live here. |
bookmarks.yaml | The bookmark bar (separate from services — no widgets) |
widgets.yaml | The info row at the top — clock, weather, resource usage, search |
docker.yaml | Tells Homepage how to reach each Docker engine |
kubernetes.yaml | Same idea for k8s (leave empty if you don’t use it) |
4. Settings: Theme, Title, Layout
Section titled “4. Settings: Theme, Title, Layout”settings.yaml sets the global look and decides how each section is arranged.
title: Sørens Mediehus
background: image: https://example.com/your-wallpaper.jpg blur: sm brightness: 50 opacity: 100
theme: darkcolor: slate
cardBlur: mdheaderStyle: clean
favicon: https://cdn-icons-png.flaticon.com/512/1946/1946488.png
useEqualHeights: truehideVersion: true
layout: Streaming: header: true style: row columns: 4 The Arr Suite: header: true style: row columns: 3 Downloads & Indexers: header: true style: row columns: 5 Infrastructure: header: true style: row columns: 6The layout keys must match the group names you use in services.yaml. columns controls how many tiles fit across before wrapping — tune per section, not globally.
5. Services — The Heart of It
Section titled “5. Services — The Heart of It”Services are grouped into sections. Each service has a link, an icon, an optional container link (for health dot), and optionally a widget.
---- Streaming: - Plex: href: http://192.168.1.248:32400 description: Movies & TV icon: plex server: local container: plex widget: type: plex url: http://192.168.1.248:32400 key: "{{HOMEPAGE_VAR_PLEX_KEY}}"
- Jellyfin: href: https://jellyfin.falseviking.uk description: Movies & TV (Open Source) icon: jellyfin server: local container: jellyfin widget: type: jellyfin url: http://jellyfin:8096 key: "{{HOMEPAGE_VAR_JELLYFIN_KEY}}"
- The Arr Suite: - Sonarr: href: http://192.168.1.248:8989 description: TV Shows icon: sonarr server: local container: sonarr widget: type: sonarr url: http://sonarr:8989 key: "{{HOMEPAGE_VAR_SONARR_KEY}}"
- Radarr: href: http://192.168.1.248:7878 description: Movies icon: radarr server: local container: radarr widget: type: radarr url: http://radarr:7878 key: "{{HOMEPAGE_VAR_RADARR_KEY}}"The important fields
Section titled “The important fields”href— where clicking the tile takes you. Usually your public URL (https://...) for the human-facing link.icon— Homepage ships with ~1500 icons. Use the app’s short name (sonarr,plex) or anmdi-*Material Design icon name (mdi-shield-search).server: local+container: sonarr— tells Homepage to watch the Docker container and show a coloured status dot (green = running, red = stopped, amber = unhealthy). The container name must match exactly.widget— the fun part. Different apps have different widget types.
Widget URLs vs href
Section titled “Widget URLs vs href”href is for the human — use the public URL. widget.url is for the container — use the internal Docker hostname and port:
# External — opens in a new tabhref: https://jellyfin.falseviking.uk
# Internal — Homepage talks to this directly from inside the networkwidget: type: jellyfin url: http://jellyfin:8096This matters because most apps on this host bind their Pangolin-routed hostname to HTTPS with a certificate valid only for the public name. Homepage’s container would fail the TLS check if you pointed the widget at the public URL. Going container-to-container over the Docker network is faster and doesn’t involve certificates.
Getting API keys
Section titled “Getting API keys”- Sonarr / Radarr / Lidarr / Prowlarr / Bazarr — Settings → General → Security → API Key
- Plex — Settings → Account → “Get current X-Plex-Token” (or grab it from
plex.tv→ devices) - Jellyfin — Dashboard → Administration → API Keys → ”+”
- Seerr / Jellyseerr — Settings → General → API Key
- qBittorrent — use the WebUI username and password (no API key system)
- Gluetun — username/password for the HTTP control server (set via
HTTP_CONTROL_SERVER_AUTHenv)
6. Bookmarks
Section titled “6. Bookmarks”Separate from services — no health dots, no widgets, just links. Good for stuff that isn’t yours: trackers, registrar, Cloudflare, documentation.
---- Developer: - GitHub: - abbr: GH href: https://github.com/ - Gitea (self-hosted): - abbr: GT href: https://gitea.falseviking.uk
- Trackers: - Aither: - abbr: AI href: https://aither.cc/ - DanishBytes: - abbr: DB href: https://danishbytes.club/7. Top-Row Widgets
Section titled “7. Top-Row Widgets”The strip at the top of the page — clock, weather, resource graph, search bar — is widgets.yaml:
---- greeting: text_size: 2xl text: "Velkommen til Sørens Mediehus"
- datetime: text_size: l format: dateStyle: long timeStyle: short hour12: false
- openmeteo: label: Copenhagen latitude: 55.6761 longitude: 12.5683 units: metric cache: 15
- resources: label: System cpu: true memory: true expanded: true disk: - / uptime: true
- search: provider: duckduckgo target: _blankresources reads directly from /proc inside the container. It sees the host’s CPU/memory because Docker doesn’t hide those by default, and it sees / because Homepage’s root filesystem is (by default) the host’s Docker storage driver layer. Add NFS mountpoints by listing them under disk: — but the path must be mounted into the Homepage container first.
8. Docker Integration
Section titled “8. Docker Integration”If you want the status dot on every tile, you need to tell Homepage how to reach the Docker engine:
local: socket: /var/run/docker.sockThat’s all for a same-host setup — the /var/run/docker.sock:/var/run/docker.sock:ro mount in the compose does the rest. For a remote Docker host over TCP, add:
nas: host: 192.168.1.154 port: 2375Then reference it on individual services with server: nas.
9. Reverse Proxy & Allowed Hosts
Section titled “9. Reverse Proxy & Allowed Hosts”The Homepage container listens on 0.0.0.0:3000 inside Docker. Front it with Pangolin (or Traefik / Caddy / nginx) the same way as any other service — route home.example.com to homepage:3000 over HTTP.
HOMEPAGE_ALLOWED_HOSTS must list every hostname you use to reach it, comma-separated. A request arriving with a Host: header that isn’t in the list is rejected with a blank page. Put your public hostname and the LAN address (if you use that too).
10. Day-to-day Usage
Section titled “10. Day-to-day Usage”Once it’s running, the workflow is:
- Add a new app to your stack. Deploy its container as usual.
- Add a block to
services.yamlunder the right group. Includecontainer: <name>so the health dot lights up. - (Optional) Add a widget. Grab the API key, add it to
.envasHOMEPAGE_VAR_FOO_KEY, reference it in the widget block. - Recreate the Homepage container if you added a new env var:
docker compose up -d. For pure config file edits (no new env vars), just reload the browser.
Over time, services.yaml becomes a living inventory of your homelab — more accurate than any docs, because if a service isn’t on Homepage you won’t use it, and if Homepage says it’s red you’ll go fix it.
11. Troubleshooting
Section titled “11. Troubleshooting”Blank page / “bad host” error. Add the hostname to HOMEPAGE_ALLOWED_HOSTS, recreate the container.
Widget shows “error”. Four usual suspects, in order:
- Wrong API key (swap and reload)
widget.urlis unreachable from inside the Homepage container — test withdocker exec homepage wget -qO- http://sonarr:8989/api/v3/system/status?apikey=...- Networks mismatch — the app and Homepage must share at least one Docker network
- App version too old for the widget (rare; check the Homepage widget docs for min versions)
Health dot is always red. Container name mismatch. docker ps shows the actual name; the YAML container: field must match exactly.
Config change doesn’t apply. Almost always a YAML parse error. docker logs homepage will show the line.
Icons showing as broken image. Either a typo in the icon name, or you’re offline — Homepage fetches many icons from a CDN by default. Self-host icons by dropping them in /config/icons/ and referencing them as /icons/myicon.png.