Build a Self-Hosted Homepage Dashboard for Homelab Services
Deploy the current Homepage project with Compose v2, configure real service links and built-in URL health checks, and keep Docker-socket access out of the first Lab so the...
Expected Outcome
A working Homepage dashboard with grouped service links, a live site-monitor status for a disposable backend, explicit allowed-host validation, and a clear security boundary for any future remote access or Docker integration.
Assumptions
A Linux host with Docker Engine and Docker Compose v2.
A hostname or IP address you will use to open Homepage from your browser.
A local/LAN-only Lab network or an authenticated reverse proxy/VPN if the dashboard will be reachable from an untrusted network.
No Docker socket access is required for this starter Lab.
Bill of Materials
Homepage v1.13.2 container image or a newer release you have reviewed.
A disposable Nginx service used to prove Homepage's `siteMonitor` feature.
A small persistent configuration directory containing `services.yaml`, `bookmarks.yaml`, `widgets.yaml`, and `settings.yaml`.
Build Steps
- Verify Compose v2 and create the configuration directory
Replace the old custom Node application with the actual Homepage project. Homepage is configured through YAML files mounted at /app/config.
Changes system state: review before running
docker --version docker compose version mkdir -p ~/homepage-lab/config && cd ~/homepage-lab
- Create the base Homepage configuration
Start with one service group and one disposable site monitor. Homepage's siteMonitor performs an HTTP HEAD request and falls back to GET, so it can show endpoint response time without Docker-socket integration.
Read-only command: verify target and scope
cat > config/services.yaml <<'EOF' - Lab Services: - Disposable Web Service: icon: nginx.png href: http://{{HOMEPAGE_VAR_LAB_HOST}}:8080/ description: Safe target for Homepage status validation siteMonitor: http://test-service/ statusStyle: basic EOF cat > config/bookmarks.yaml <<'EOF' - Operations: - Homepage Documentation: - abbr: HP href: https://gethomepage.dev/ - Maintenance Notes: - abbr: MN href: https://example.com/replace-with-your-maintenance-runbook EOF cat > config/widgets.yaml <<'EOF' - resources: cpu: true memory: true disk: / EOF cat > config/settings.yaml <<'EOF' title: Homelab Operations statusStyle: basic disableIndexing: true EOF - Define Homepage and the disposable test service
Set the exact host/IP and port you will use in the browser. Homepage requires HOMEPAGE_ALLOWED_HOSTS for non-localhost access and documents the value as a comma-separated, no-space list. The Lab deliberately does not mount /var/run/docker.sock; direct Docker integration is optional and carries broader host-control implications.
Changes system state: review before running
cat > .env <<'EOF' LAB_HOST=<homepage-host-or-ip> EOF cat > compose.yaml <<'EOF' services: homepage: image: ghcr.io/gethomepage/homepage:v1.13.2 restart: unless-stopped environment: HOMEPAGE_ALLOWED_HOSTS: "${LAB_HOST}:3000" HOMEPAGE_VAR_LAB_HOST: "${LAB_HOST}" PUID: "${PUID:-1000}" PGID: "${PGID:-1000}" ports: - "3000:3000" volumes: - ./config:/app/config test-service: image: nginx:alpine restart: unless-stopped ports: - "8080:80" EOF sed -i "s/^LAB_HOST=.*/LAB_HOST=$(hostname -I | awk '{print $1}')/" .env printf 'PUID=%s\nPGID=%s\n' "$(id -u)" "$(id -g)" >> .env docker compose config --quiet - Start Homepage and validate host handling
Start the stack and browse to http://<LAB_HOST>:3000. If Homepage rejects the request, read the log entry and add the exact host:port to HOMEPAGE_ALLOWED_HOSTS rather than disabling host validation.
Changes system state: review before running
docker compose up -d docker compose ps docker compose logs --tail=100 homepage LAB_HOST=$(grep '^LAB_HOST=' .env | cut -d= -f2-); curl -I -H "Host: ${LAB_HOST}:3000" http://127.0.0.1:3000/ - Validate the built-in service monitor
Homepage should show the disposable service as UP with a response time while test-service is running. The href opens the backend from the browser, while siteMonitor uses the internal Compose-network address so the dashboard can measure the service directly.
Read-only command: verify target and scope
docker compose exec -T homepage getent hosts test-service curl -I http://$(grep '^LAB_HOST=' .env | cut -d= -f2-):8080/
- Run a controlled status transition
Stop only the disposable Nginx service, wait for Homepage's next monitor cycle, and confirm the tile changes to DOWN. Start it again and verify the status recovers. This proves the displayed status is tied to an actual request rather than a hard-coded tile.
Read-only command: verify target and scope
docker compose stop test-service
Manual or UI step
sleep 20
Read-only command: verify target and scope
docker compose start test-service
- Add real links carefully
Replace the placeholder maintenance bookmark and add a few real internal services to services.yaml. Use siteMonitor only for URLs Homepage can reach without an interactive login; the project documents that monitors behind authentication may require an internal health URL. Keep API keys out of plain YAML by using Homepage's supported HOMEPAGE_VAR_ or HOMEPAGE_FILE_ substitutions if you later add service widgets.
- Understand the remote-access boundary
Homepage's current security guidance says its host check is only a best-effort local guard. If this dashboard becomes reachable from an untrusted network, put it behind a reverse proxy and/or VPN that enforces authentication, TLS, and Host-header controls. Do not make internet exposure a generic next step.
Validation
Homepage starts from the official v1.13.2 image using Docker Compose v2 and reads configuration from `/app/config`.
The dashboard is accessible only through host values explicitly allowed by `HOMEPAGE_ALLOWED_HOSTS`.
The disposable service tile shows a live site-monitor response while Nginx is running.
Stopping the disposable service causes the status to show failure and restarting it restores healthy status.
The starter Lab works without mounting the Docker socket into Homepage.
The operator can explain why untrusted-network exposure requires an authenticated TLS/VPN boundary beyond Homepage's built-in host validation.
Troubleshooting
If Homepage reports host validation failure, copy the exact host value from the Homepage log into `HOMEPAGE_ALLOWED_HOSTS`; multiple entries are comma-separated with no spaces.
Read-only command: verify target and scope
docker compose logs --tail=100 homepage
If the tile link works from the browser but `siteMonitor` is DOWN, test the internal URL from the Homepage container. A browser-visible URL and a container-reachable monitor URL can legitimately be different.
Read-only command: verify target and scope
docker compose exec -T homepage getent hosts test-service
If configuration changes do not appear, validate YAML indentation and inspect Homepage logs before adding custom JavaScript or replacing the container image.
Cleanup or Rollback
Restart `test-service` after outage validation if you want the Lab to remain healthy.
Stop the Lab with `docker compose down`; preserve the `config` directory if you want to keep the dashboard definition.
Remove the disposable backend and its bookmark/service entry once you replace it with real non-critical services.
Next Improvements
Put Homepage behind an authenticated HTTPS reverse proxy or VPN before exposing it to any untrusted network.
Add service-specific widgets using `HOMEPAGE_VAR_` or `HOMEPAGE_FILE_` secret substitution instead of embedding API keys directly in YAML.
Evaluate Docker integration only if container status adds enough value to justify the additional Docker-socket or proxy boundary.
