Skip to content
Stuff I'm Up To
Go back
Homepage Dashboard

Homepage Dashboard

I used Homarr for a while as the dashboard in front of my Servarr apps, but it kept failing on me, so I moved to Homepage instead.

The trade-off going in is that Homepage takes a lot more setting up than Homarr. Homarr is configured through its own web UI - add a tile, point it at a service, done. Homepage has no UI for any of that. Everything - the services it shows, the bookmarks, the widgets, even the layout - lives in a handful of YAML files that you edit directly and the container picks up.

Here’s what it actually looks like once it’s all wired up:

Running It

Here’s the compose file I run it with:

services:
  homepage:
    image: ghcr.io/gethomepage/homepage:latest
    container_name: homepage
    ports:
      - 3004:3000
    volumes:
      - ${PWD}/config:/app/config # Make sure your local config directory exists
      - /var/run/docker.sock:/var/run/docker.sock:ro # (optional) For docker integrations
    restart: unless-stopped
    environment:
      HOMEPAGE_ALLOWED_HOSTS: ${HOMEPAGE_ALLOWED_HOSTS}
  glances:
    image: nicolargo/glances:latest
    restart: unless-stopped
    pid: host
    network_mode: host
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /mnt/data:/mnt/data:ro
    environment:
      - GLANCES_OPT=-w

The Docker socket mount is optional - it lets Homepage auto-discover containers and their labels rather than you having to list every service by hand. Glances runs alongside it in its own container, in web-server mode (-w), and Homepage’s glances widget talks to that over HTTP for the system stats further down.

Configuring the Dashboard

The config directory is where the actual setup happens - settings.yaml for overall layout, bookmarks.yaml for plain links, docker.yaml for Docker host credentials, and services.yaml for the dashboard itself: the services, grouped into sections, each with an icon and, optionally, a live widget.

This is the bulk of my services.yaml, with the credentials taken out:

- Networking:
    - ADGuard:
        href: https://ns0.mydomain.com
        icon: adguard-home.png
        widget:
          type: adguard
          url: https://ns0.mydomain.com
          fields: ["queries", "blocked", "latency"]
          username: admin
          password: your-adguard-password
    - Containers:
        href: http://192.168.1.10:3030
        icon: dockhand.png
        widget:
          type: dockhand
          url: http://192.168.1.10:3030
    - Speedtest:
        href: http://192.168.1.10:5216
        icon: myspeed.png
        widget:
          type: myspeed
          url: http://192.168.1.10:5216

- Media:
    - Radarr:
        href: https://blackpearl.mydomain.com/movies/
        icon: radarr.png
        widget:
          type: radarr
          url: https://blackpearl.mydomain.com/movies/
          key: your-radarr-api-key
    - Sonarr:
        href: https://blackpearl.mydomain.com/tv/
        icon: sonarr.png
        widget:
          type: sonarr
          url: https://blackpearl.mydomain.com/tv/
          key: your-sonarr-api-key
    - Prowlarr:
        href: https://blackpearl.mydomain.com/search/
        icon: prowlarr.png
        widget:
          type: prowlarr
          url: https://blackpearl.mydomain.com/search/
          key: your-prowlarr-api-key
    - SABnzbd:
        href: http://192.168.1.10:8080
        icon: sabnzbd.png
        widget:
          type: sabnzbd
          url: http://192.168.1.10:8080
          key: your-sabnzbd-api-key

- Calendar:
    - Calendar:
        widget:
          type: calendar
          firstDayInWeek: monday
          view: monthly
          maxEvents: 10
          showTime: true
          integrations:
            - type: sonarr
              service_group: Media
              service_name: Sonarr
              color: teal
              baseUrl: https://blackpearl.mydomain.com/tv
            - type: radarr
              service_group: Media
              service_name: Radarr
              color: yellow
              baseUrl: https://blackpearl.mydomain.com/movies

- Server:
    - CPU:
        widget:
          type: glances
          url: http://192.168.1.10:61208
          version: 4
          metric: cpu
          refreshInterval: 5000
    - Memory:
        widget:
          type: glances
          url: http://192.168.1.10:61208
          version: 4
          metric: memory
          refreshInterval: 5000
    - Network:
        widget:
          type: glances
          url: http://192.168.1.10:61208
          version: 4
          metric: network:eno1
          refreshInterval: 5000
    - "Data Volume":
        widget:
          type: glances
          url: http://192.168.1.10:61208
          version: 4
          metric: fs:/mnt/data
          refreshInterval: 5000

That’s four groups doing quite different jobs:

That password and those key values are real credentials for the service they sit next to - an AdGuard admin password, and the Sonarr/Radarr/Prowlarr/SABnzbd API keys, all found in each app’s own settings. I’ve blanked mine out above; Homepage also supports pulling values like this out of a separate secrets file instead of writing them into services.yaml directly, worth doing if the file ever ends up somewhere more shared than your own machine.

Putting It Behind Caddy

All of this sits behind Caddy on the same host, one site block covering Homepage and every app it links to:

blackpearl.mydomain.com {
#  error 503 # Maintenance Mode

  redir /tv /tv/
  redir /movies /movies/
  redir /books /books/
  redir /search /search/
  redir /watch /watch/
  redir /sabnzbd /sabnzbd/
  redir /grafana /grafana/
  redir /access /access/

#  import waf

  reverse_proxy /* {
    to 127.0.0.1:3004
    health_uri http://127.0.0.1:3004/
  }
  reverse_proxy /tv/* {
    to 127.0.0.1:8989
    health_uri http://127.0.0.1:8989/tv
  }
  reverse_proxy /movies/* {
    to 127.0.0.1:7878
    health_uri http://127.0.0.1:7878/movies
  }
  reverse_proxy /books/* {
    to 127.0.0.1:8787
    health_uri http://127.0.0.1:8787/books
  }
  reverse_proxy /search/* {
    to 127.0.0.1:9696
    health_uri http://127.0.0.1:9696/search
  }
  reverse_proxy /watch/* {
    to 127.0.0.1:3080
    health_uri http://127.0.0.1:3080
  }
  reverse_proxy /sabnzbd/* {
    to 127.0.0.1:8080
    health_uri http://127.0.0.1:8080/sabnzbd/
  }
  reverse_proxy /grafana/* {
    to 127.0.0.1:3001
    health_uri http://127.0.0.1:3001/grafana/
  }

  handle /access/* {
    root * /usr/share/caddy/html/access
    try_files * /index.html
    file_server
  }

  reverse_proxy /ws {
    to 127.0.0.1:7890
  }

  tls {
    dns cloudflare {$CLOUDFLARE_API_KEY}
    resolvers 1.1.1.1
  }

  handle_errors {
    root * /usr/share/caddy/html
    @custom_err file /{err.status_code}.html /50x.html
    handle @custom_err {
      rewrite * {file_match.relative}
      file_server
    }
    respond "{err.status_code} {err.status_text}"
  }

  #import subdomain-log blackpearl.mydomain.com

}

The root path (/*) goes to Homepage on 3004, the same host port from the compose file above, and everything else is a path-based route to one of the Servarr apps or another service on the same box, each with its own health_uri so Caddy knows if it’s actually up. The tls block gets its certificate via a DNS challenge against Cloudflare, with the API token coming from an environment variable rather than sitting in the file, and handle_errors serves a custom page instead of Caddy’s default when something behind it is down.

References

Homepage documentation

See also: Servarr Apps


Share this post:

Previous Post
AWS SSH using SSM
Next Post
OpenMediaVault 8 on a Mini PC