DEV Community

DeployLark
DeployLark

Posted on Originally published at deploylark.com

Homepage Dashboard Docker Setup: Config, Widgets and Labels

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.0 with ./config mounted at /app/config and port 3000 bound to 127.0.0.1. The first start writes default YAML files into that folder.
  • Set HOMEPAGE_ALLOWED_HOSTS to every host and port people or monitors use, comma-separated with no spaces. Only localhost:3000 and 127.0.0.1:3000 work without it.
  • After any env change, recreate the container with docker compose up -d. A docker compose restart keeps the old value.
  • For Docker stats and label discovery, point docker.yaml at a tecnativa/docker-socket-proxy container with CONTAINERS=1 and POST=0 instead 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:

Diagram of the stack on the Compose network hp_default. A browser sends requests with Host homepage.lab.test:3000 to the homepage container, v2.6.0 on 127.0.0.1:3000, where the host is checked against HOMEPAGE_ALLOWED_HOSTS. Homepage reads labels, status and stats from dockerproxy, socket-proxy v0.5.0 with CONTAINERS=1 and POST=0, which mounts docker.sock read-only. Homepage also calls uptime-kuma 2.5.6 for the uptimekuma widget and whoami v1.12.0 for a customapi widget. Uptime Kuma polls homepage:3000/api/healthcheck, so homepage:3000 is an allowed host too.

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:
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Then start it:

mkdir -p config
docker compose up -d
curl -s http://127.0.0.1:3000/api/healthcheck
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Homepage v2.6.0 dashboard in the dark slate theme. The top bar shows CPU at 3%, 8 GiB memory free, 116 GB disk free, the date Oct 20, 2026, 10:14 AM and a search box. The Monitoring group has an Uptime Kuma card with container stats, 1% CPU, 145 MB memory, 143 kB RX and 3.58 MB TX, and the Uptime Kuma widget showing 2 sites up, 0 sites down and 67% uptime. The Lab group has a whoami card, discovered from Docker labels, with a customapi widget showing the container hostname and the remote address 172.21.0.5:43146. A Docs bookmarks section lists Homepage docs and Uptime Kuma wiki. The footer reads v2.6.0 (51cc168, Oct 17, 2026).

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

Terminal capture from the lab. A curl request with Host homepage.lab.test:3000 to 127.0.0.1:3000/api/services returns HTTP 400 with the JSON error Host validation failed. See logs for more details. The Homepage log shows error: Host validation failed for: homepage.lab.test:3000 and the hint to set the HOMEPAGE_ALLOWED_HOSTS environment variable. After setting HOMEPAGE_ALLOWED_HOSTS=homepage.lab.test:3000,homepage:3000 in .env and running docker compose up -d homepage, the same request returns 200 and a request with Host evil.example:3000 still returns 400.

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.

Uptime Kuma 2.5.6 detail page for the Homepage monitor, which checks http://homepage:3000/api/healthcheck every 60 seconds. The heartbeat bar shows two red beats followed by green ones and the status is Up. Uptime for 24 hours, 30 days and 1 year reads 66.67 percent. The event list shows Down at 2026-10-20 10:12:15 with Request failed with status code 400, then Up at 10:14:15 with 200 - OK.

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
Enter fullscreen mode Exit fullscreen mode

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)