Self-Hosted Services and ProductivityDocs and File WorkflowsIntermediate60-90 minutesLab

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...

Last reviewed8/19/2026
Homepage dashboardsservice health tilesmaintenance links
Homepage v1.13.2Docker Compose v2YAMLNginx test service

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

  1. 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
  2. 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
  3. 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
  4. 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/
  5. 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/
  6. 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
  7. 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.

  8. 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.

References

Keep Moving

Build on what you just completed

Continue with a related Lab or return to this build path for a different implementation.