Homepage is a static start page for a homelab. You describe services, bookmarks and widgets in YAML files, and it shows live data from the apps behind them, such as container stats, uptime and queue counts. On October 20, 2026, the gethomepage/homepage repository had about 33,200 GitHub stars, and v2.6.0 had shipped three days earlier. This guide runs v2.6.0 with Docker Compose, wires up an Uptime Kuma widget, discovers a container from Docker labels through a read-only socket proxy and fixes the Host validation failed error that trips up most first installs.
TL;DR:
- Run
ghcr.io/gethomepage/homepage:v2.6.0with./configmounted at/app/configand port 3000 bound to127.0.0.1. The first start writes default YAML files into that folder. - Set
HOMEPAGE_ALLOWED_HOSTSto every host and port people or monitors use, comma-separated with no spaces. Onlylocalhost:3000and127.0.0.1:3000work without it. - After any env change, recreate the container with
docker compose up -d. Adocker compose restartkeeps the old value. - For Docker stats and label discovery, point
docker.yamlat atecnativa/docker-socket-proxycontainer withCONTAINERS=1andPOST=0instead of mounting the socket into Homepage. - Add
homepage.*labels to any container and it shows up on the dashboard, widget included.
Install Homepage with Docker Compose
The compose file
The Docker install docs show a single homepage service with an optional socket mount. This file adds a socket proxy, as the Docker integration docs suggest, plus two services for Homepage to watch: Uptime Kuma 2.5.6 and the tiny traefik/whoami web server. The official examples use :latest. Every tag here is pinned to what ran:
name: hp
services:
dockerproxy:
image: tecnativa/docker-socket-proxy:v0.5.0
environment:
- CONTAINERS=1
- POST=0
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
restart: unless-stopped
homepage:
image: ghcr.io/gethomepage/homepage:v2.6.0
ports:
- "127.0.0.1:3000:3000"
volumes:
- ./config:/app/config
environment:
HOMEPAGE_ALLOWED_HOSTS: ${HOMEPAGE_ALLOWED_HOSTS:-}
depends_on:
- dockerproxy
restart: unless-stopped
uptime-kuma:
image: louislam/uptime-kuma:2.5.6
ports:
- "127.0.0.1:3001:3001"
volumes:
- kuma-data:/app/data
restart: unless-stopped
whoami:
image: traefik/whoami:v1.12.0
ports:
- "127.0.0.1:8081:80"
labels:
- homepage.group=Lab
- homepage.name=whoami
- homepage.icon=traefik.png
- homepage.href=http://127.0.0.1:8081/
- homepage.description=Discovered from Docker labels
- homepage.widget.type=customapi
- homepage.widget.url=http://whoami/api
- homepage.widget.mappings[0].label=Hostname
- homepage.widget.mappings[0].field=hostname
- homepage.widget.mappings[1].label=Remote
- homepage.widget.mappings[1].field=remoteAddr
restart: unless-stopped
volumes:
kuma-data:
Put the allowed hosts in a .env file next to it. The section on host validation below explains each entry:
HOMEPAGE_ALLOWED_HOSTS=homepage.lab.test:3000,homepage:3000
Then start it:
mkdir -p config
docker compose up -d
curl -s http://127.0.0.1:3000/api/healthcheck
The health check answered 2.9 seconds after docker compose up -d. That curl works without an allowed-hosts entry only because the host port is 3000. When I published Homepage on 127.0.0.1:3900 instead, the same health check on the server returned 400 until 127.0.0.1:3900 was on the list. By default the container runs as root. The docs support PUID and PGID for a non-root user, and with the socket proxy Homepage does not need socket access at all.
First start and the default config files
Start with an empty ./config folder. After the first start and a few page loads, mine held settings.yaml, services.yaml, widgets.yaml, bookmarks.yaml, docker.yaml, kubernetes.yaml and proxmox.yaml, plus empty custom.css and custom.js files and a logs folder. Most of the YAML files start with a comment that links to their docs page. The default services.yaml holds three placeholder groups pointing at http://localhost/, the default widgets.yaml shows CPU, memory and disk usage plus a DuckDuckGo search box, and the default settings.yaml only has placeholder weather API keys.
Edits to these files do not need a container restart. The settings docs say to click the refresh icon at the bottom right of the page to clear cached dashboard data. Logs go to config/logs/homepage.log as well as docker compose logs homepage.
Add services and info widgets
services.yaml with an Uptime Kuma widget
A service entry is a card with a name, icon, link and description. Add server and container to show a status dot and container stats, and add a widget block to pull data from the app API. The Uptime Kuma widget reads a single status page, so create one in Uptime Kuma first. This one has the slug lab and two HTTP monitors, one for whoami and one for the Homepage health check:
---
- Monitoring:
- Uptime Kuma:
icon: uptime-kuma.png
href: http://127.0.0.1:3001/
description: Status page lab
server: lab-docker
container: hp-uptime-kuma-1
showStats: true
widget:
type: uptimekuma
url: http://uptime-kuma:3001
slug: lab
The widget URL uses the Compose service name, because the request comes from the Homepage container, not from the browser. Homepage looks up icon names in the Dashboard Icons set, so you only type the name.
The 67% is the average 24-hour uptime of every monitor on the status page, which is how the widget computes it. When the screenshot was taken, whoami stood at 100% and the Homepage monitor at 33%, because two of its three checks had failed with HTTP 400. The host validation section below explains why.
widgets.yaml and settings.yaml
Info widgets sit in the bar across the top. This file keeps the resources and search widgets from the defaults and adds a clock:
---
- resources:
cpu: true
memory: true
disk: /
- datetime:
text_size: md
format:
dateStyle: medium
timeStyle: short
- search:
provider: duckduckgo
target: _blank
Theming and layout live in settings.yaml. The settings docs cover the full list. These keys set a dark theme with the slate palette, boxed headers, status dots instead of text, and a two-column row layout for each group:
---
title: Lab dashboard
theme: dark
color: slate
headerStyle: boxed
statusStyle: dot
layout:
Monitoring:
style: row
columns: 2
Lab:
style: row
columns: 2
Use background for a wallpaper image and custom.css in the config folder for anything the settings do not cover.
Docker integration and label discovery
docker.yaml through a socket proxy
Homepage talks to the Docker API for container status, CPU and memory stats, and label discovery. The docs offer two ways in. Mounting /var/run/docker.sock into Homepage works, but the docs note that "In order to use the socket directly homepage must be running as root", and socket access gives full control of the host. The :ro mount flag does not change that, because it doesn't limit the Docker API. The safer route is a socket proxy that only exposes read endpoints. Point docker.yaml at it:
---
lab-docker:
host: dockerproxy
port: 2375
lab-docker is just a name. Service entries refer to it with server: lab-docker. The proxy listens on port 2375 inside the Compose network only, with no published port. With CONTAINERS=1 and POST=0, curl from another container on the same network got these results:
GET /containers/json 200
POST /containers/<id>/restart 403
GET /images/json 403
So Homepage can list containers and read stats, but it cannot restart anything or browse images. If you run Docker Swarm, the docs add SERVICES=1 and TASKS=1 to the proxy.
Labels on the container
Automatic discovery reads homepage.* labels from every container the Docker server can see. The keys mirror the YAML in services.yaml, written with dots. The whoami container in the compose file above carries a group, name, icon, link and description, plus a customapi widget that calls the whoami JSON endpoint and maps two fields:
labels:
- homepage.group=Lab
- homepage.name=whoami
- homepage.icon=traefik.png
- homepage.href=http://127.0.0.1:8081/
- homepage.description=Discovered from Docker labels
- homepage.widget.type=customapi
- homepage.widget.url=http://whoami/api
- homepage.widget.mappings[0].label=Hostname
- homepage.widget.mappings[0].field=hostname
- homepage.widget.mappings[1].label=Remote
- homepage.widget.mappings[1].field=remoteAddr
Nothing about whoami is in services.yaml. After docker compose up -d, the Lab group appeared with the whoami card, a green status dot from the container state and the two mapped values, visible in the dashboard screenshot above. The Remote value is an address on the Compose network, because Homepage makes the widget request from its own container, not from the browser. The docs say the server and container values are inferred for discovered services, and the whoami card needed neither label. To keep one dashboard from picking up labels meant for another, the docs describe instanceName in settings.yaml with matching homepage.instance.<name>.* labels.
The same pattern works for real apps that have a native widget. A Paperless-ngx or Nginx Proxy Manager container can carry its own homepage.widget.type and API credentials as labels, so the dashboard entry lives next to the service it describes.
Fix Host validation failed with HOMEPAGE_ALLOWED_HOSTS
What the error looks like
Since v1.0.0, released on March 14, 2025, Homepage checks the Host header of every API request. The installation docs list it as the one required variable for reaching Homepage through any URL other than localhost. If the host is not on the list, the page and its API calls, /api/healthcheck included, return HTTP 400. This is the response to Host: homepage.lab.test:3000 before the variable was set:
The log line names the exact host and port that failed. Copy that value into the variable. Requests to http://127.0.0.1:3000 and http://localhost:3000 always pass, which is why a first test on the server itself often works and the same dashboard breaks from a laptop on the LAN. That only covers port 3000. If you map another host port, such as 8080:3000, add 127.0.0.1:8080 or the LAN address with :8080, as users found in discussion #4920.
A second case is easy to miss. Uptime Kuma checked http://homepage:3000/api/healthcheck from inside the Compose network, so its requests arrived with Host: homepage:3000. That monitor went Down at 10:12:15 with HTTP 400 and came back Up at 10:14:15, right after homepage:3000 joined the list. Any monitor, reverse proxy or script that reaches Homepage by container name needs that name on the list too, port included. Our Uptime Kuma guide covers setting up that kind of HTTP monitor.
This screenshot came a few minutes later than the dashboard one. By then four of the monitor's six checks had passed, so its own uptime read 66.67%.
The value to set, and recreate the container
The value is a comma-separated list with no spaces. Include the port whenever the browser URL has one:
HOMEPAGE_ALLOWED_HOSTS=homepage.lab.test:3000,homepage:3000
Behind a reverse proxy on port 443, the browser sends the bare domain, such as dash.example.com, so that is the entry. With that list, homepage.lab.test:3000 and homepage:3000 returned 200, and evil.example:3000 still returned 400.
Then recreate the container. I added a third host to .env and ran docker compose restart homepage. Inside the container, the variable still held the old two hosts and the new host got 400. After docker compose up -d homepage, the variable held all three and the request returned 200. Maintainer shamoon wrote in discussion #5033: "You need to re-create a container when you change env variables, not just restart." Check what the container really sees with docker compose exec homepage env | grep ALLOWED.
Setting the variable to * turns the check off. The docs do not recommend it and add that "the built-in host check is a best-effort guard for local setups and is not a substitute for edge protections."
Secure and run Homepage
Put authentication in front of it
Homepage shows internal hostnames and links, and its API proxy calls your services with the keys from your config. Keep it on 127.0.0.1 or a private network and publish it through a reverse proxy with TLS, as in the Nginx Proxy Manager guide. Since v2.0.0 Homepage also includes an optional login gate, which this guide doesn't turn on. The installation docs list HOMEPAGE_AUTH_ENABLED=true, a HOMEPAGE_AUTH_SECRET of at least 32 characters, HOMEPAGE_EXTERNAL_URL, and either HOMEPAGE_AUTH_PASSWORD or OIDC settings. The same docs warn that Homepage does not rate limit password attempts, so a proxy or VPN should still sit in front of any public instance. Keep to the newest release as well. v2.6.0 fixes GHSA-qxcr-4rv3-v23r, a high-severity bypass of that login gate in versions 2.0.0 to 2.5.0.
API keys do not need to sit in plain YAML. Per the Docker install docs, the value of any HOMEPAGE_VAR_NAME environment variable replaces {{HOMEPAGE_VAR_NAME}} in the config, and HOMEPAGE_FILE_NAME points at a file, such as a Docker secret, whose contents replace {{HOMEPAGE_FILE_NAME}}.
Resource use
Homepage is light. At idle, docker stats showed these averages for the four containers:
| Container | Memory | CPU |
|---|---|---|
| homepage v2.6.0 | 116.5 MiB | 0.00% |
| uptime-kuma 2.5.6 | 139.6 MiB | 0.41% |
| docker-socket-proxy v0.5.0 | 22.3 MiB | 0.00% |
| whoami v1.12.0 | 2.4 MiB | 0.00% |
| Total | 280.8 MiB | 0.42% |
Homepage never went above 0.04% CPU. It fits next to heavier apps on a small VPS, such as a Coolify host or a box that also runs Plausible.
FAQ
Host validation and setup
Why does the error stay after setting HOMEPAGE_ALLOWED_HOSTS?
Usually the container was restarted, not recreated, so it still has the old environment. Run docker compose up -d and check with docker compose exec homepage env. In discussion #5033 and discussion #5412, the maintainer's answer to this error was to recreate the container, not just restart it. The #5412 user ran Homepage through TrueNAS apps and then Dockge, so check how your stack manager recreates a container.
Does HOMEPAGE_ALLOWED_HOSTS need the port?
Yes, whenever the URL has one. The check compares the full Host header, so 192.168.1.10:3000 and 192.168.1.10 are different entries. Discussion #4920 also shows that a reverse proxy can change the Host header, so match the value printed in the log.
Can the list contain spaces?
No. The docs specify a comma-separated list with no spaces, and discussion #4789 traces a failing setup to a space after a comma.
Widgets and Docker
Why does a widget show API Error?
The widget call from Homepage to the service failed. The troubleshooting page suggests the API Error Information button on the widget, then config/logs/homepage.log, and LOG_LEVEL=debug for more detail. Check that the widget URL works from inside the Homepage container. A container name like http://uptime-kuma:3001 works there, localhost does not.
Why are containers with homepage labels not showing up?
Homepage needs a working Docker connection in docker.yaml, and the labels should include homepage.group and homepage.name, as in the docs examples. With the socket mounted directly, Homepage must run as root or have socket access, a common cause in discussion #3124. A socket proxy with CONTAINERS=1 avoids the permission problem.
Is mounting docker.sock into Homepage safe?
It works, but it hands the container full control of Docker. The Docker integration docs point to a socket proxy for that reason. With POST=0, the proxy above refused a container restart with 403 while still serving the container list.
Originally published on DeployLark: Homepage Dashboard Docker Setup: Config, Widgets and Labels




Top comments (0)