Skip to main content

Dashboard

Docker Compose services that show links, widgets, and live status for the other self-hosted services and machines on the network: Apache in front as reverse proxy and login gate, then Glance and Homepage behind it. They cover Emby, Jellyfin, Navidrome, Devops Server, its PyPI mirror, Code Server, qBittorrent, Nextcloud, Vikunja, Nginx Proxy Manager, OpenWRT, S3, Vault, and per-machine Glances stats.

Glance · Community Widgets

Homepage

Architecture​

client → apache:10333 (form auth, session cookie)
├─ /my-dashboard* → glance:10330 (proxied, base-url /my-dashboard)
├─ /my-dashboardmanifest.json → glance:10330/manifest.json (PWA manifest workaround, see httpd.conf)
├─ /login.html, /dologin, /dologout, /file-storage → served by Apache itself
└─ / (everything else) → homepage:10331

Apache (stack/apache/httpd.conf) is the only container with a host port (10333:10333 for the authenticated port, 10334:10334 for the LAN-only port). glance and homepage are reachable only from the dashboard bridge network.

mod_auth_form puts every path except /login.html, /dologin, /dologout, and the PWA manifest behind /usr/local/apache2/conf/.htpasswd. stack/apache/entrypoint.sh writes that file at container start from DASHBOARD_VAR_USER and DASHBOARD_VAR_PASSWORD, and stack/apache/htdocs/login.html is the login page. /file-storage (WebDAV) uses plain HTTP Basic auth against the same .htpasswd, because WebDAV clients can't fill in an HTML form or keep a session cookie.

On 10333, mod_remoteip takes the visitor's address from X-Forwarded-For when the connection comes from a loopback/private address (Nginx Proxy Manager) and skips Cloudflare's edge ranges, so access logs show the real client IP. Port 10334 ignores that header and checks the direct peer.

Port 10334 serves the same content with no login, but only to clients in 10.8.33.0/24 and 10.8.34.0/24 (hardcoded in stack/apache/httpd.conf); any other source gets 403. Point the reverse proxy and Cloudflare at 10333 only, and keep 10334 unforwarded and unreachable from outside the LAN.

entrypoint.sh also picks a new random SessionCryptoPassphrase on every start to encrypt the session cookie. Restarting the container logs everyone out.

  • Glance (stack/glance/config/) renders the "Applications" page (bookmark groups for this dashboard's own links, managed cloud/domain admin consoles, this stack's self-hosted services, and a Nginx Proxy Manager widget listing every enabled proxy host) and the "Feed" page (weather, markets, Twitch, a Vikunja taskboard, Reddit/Hacker News/Lobsters, videos, GitHub notifications, recent Devops Server repo activity, and a GitHub releases tracker).
  • Homepage (stack/homepage/config/) renders the service/machine tree: one entry per self-hosted app under Applications, and one entry per physical host under Machines built from glances-widget metric blocks (host info, CPU, memory, GPU, sensors, filesystems, disks, network, processes, containers).

Commands​

docker-compose.yml, apache/, glance/, and homepage/ all live under stack/, so docker compose commands run from there.

Run the stack:

cd stack
docker compose up -d
docker compose pull && docker compose up -d # pick up new pinned image tags
docker compose logs -f <apache|glance|homepage>

Environment variables supply every DASHBOARD_VAR_* / MACHINE_* / DOCKER_MACHINE_TLS_* value the compose file interpolates. See the table below for what each variable is. For local docker compose runs, create a stack/.env file with these variables. For remote deployments, the Python deployer reads environment variables from the current process and generates the .env file on the remote machine. Importing the dashboard package also loads the nearest .env above src/dashboard/ (the repo root's .env in a checkout), without overriding variables the process already has. The dav-data volume binds to the fixed host path /srv/dashboard/dav, which must exist (sudo mkdir -p /srv/dashboard/dav) on whichever host runs the stack.

Deploy to a remote machine:

DASHBOARD_REMOTE_MACHINE=<host> python -m dashboard.deployer

The deployer copies stack/ to /app/dashboard/stack on DASHBOARD_REMOTE_MACHINE (default rb5-m3) over SSH/SFTP. The deployer connects with paramiko, which doesn't read ~/.ssh/config: the hostname must resolve through DNS or /etc/hosts, the login user is your local username, and authentication uses your SSH agent or the default keys under ~/.ssh/. The deployer:

  • Validates all required environment variables from the process environment.
  • Checks for an existing stack on the remote. If the compose file is present but .env is missing, writes .env first (docker compose down needs it for variable interpolation), then runs docker compose --progress plain down -v (removes volumes).
  • Removes any existing stack directory on the remote.
  • Creates /app/dashboard/stack and /srv/dashboard/dav directories. WebDAV data lives outside the app directory, so redeploys leave it alone.
  • Uploads stack/ via SFTP.
  • Generates and writes .env to the remote stack directory with all validated environment variables.
  • Rebuilds the images with docker compose --progress plain build --no-cache and starts the stack with docker compose --progress plain up -d --force-recreate.

Using the deployer:

uv --offline run --no-sync --no-progress dashboard-deployer

# With explicit remote machine
DASHBOARD_REMOTE_MACHINE=rb5-m3 dashboard-deployer

Development​

The pre-commit hook runs gitleaks (must be installed and on PATH) to scan for secrets. Enable it once per clone:

chmod +x .git-hooks/*
git config --local core.hooksPath .git-hooks

A .gitea/workflows/gitleaks.yml CI job runs the same scan on every push, pull request, and a daily schedule.

Environment variables​

docker-compose.yml maps each DASHBOARD_VAR_* and MACHINE_* value Homepage uses from .env to a HOMEPAGE_VAR_* environment variable, which Homepage's config files read as {{HOMEPAGE_VAR_*}}. The glance service gets the values it needs (DASHBOARD_VAR_*, MACHINE_OPENWRT_IP, MACHINE_S1_DEV_IP, GH_PROD_API_TOKEN, DASHBOARD_GLANCE_CACHE_SEC) under the same names in its environment: block and reads them as ${NAME} in its YAML.

Every variable below is required. docker-compose.yml interpolates each one with ${VAR:?error}, so docker compose up fails if one is missing from .env. Without that, the widget would render blank.

Service credentials and endpoints​

VariableDescription
DASHBOARD_VAR_EMBY_API_ENDPOINTEmby server API endpoint, like https://127.0.0.1:3466.
DASHBOARD_VAR_EMBY_SERVEREmby server address, like 127.0.0.1.
DASHBOARD_VAR_EMBY_API_PASSWORDEmby server API password.
DASHBOARD_VAR_DEVOPS_SERVER_HOSTDevops Server server address, like 127.0.0.1.
DASHBOARD_VAR_DEVOPS_SERVER_API_ENDPOINTDevops Server server API endpoint, like https://127.0.0.1:3000.
DASHBOARD_VAR_DEVOPS_SERVER_API_PASSWORDDevops Server server API password.
DASHBOARD_VAR_PYPI_MIRROR_HOSTDevops Server PyPI mirror (devpi) server address, like 127.0.0.1.
DASHBOARD_VAR_PYPI_MIRROR_API_ENDPOINTDevops Server PyPI mirror (devpi) API endpoint, like http://127.0.0.1:5502.
DASHBOARD_VAR_JELLYFIN_API_ENDPOINTJellyfin server API endpoint, like https://127.0.0.1:3459.
DASHBOARD_VAR_JELLYFIN_SERVERJellyfin server address, like 127.0.0.1.
DASHBOARD_VAR_JELLYFIN_API_PASSWORDJellyfin server API password.
DASHBOARD_VAR_NAVIDROME_HOSTNavidrome server address, like 127.0.0.1.
DASHBOARD_VAR_NAVIDROME_URLNavidrome server URL, like http://127.0.0.1:7644.
DASHBOARD_VAR_NAVIDROME_USERNAMENavidrome username.
DASHBOARD_VAR_NAVIDROME_RANDOM_SALTRandom salt used to compute DASHBOARD_VAR_NAVIDROME_PASSWORD_MD5.
DASHBOARD_VAR_NAVIDROME_PASSWORD_MD5MD5 hash of the Navidrome password concatenated with DASHBOARD_VAR_NAVIDROME_RANDOM_SALT (Subsonic API token auth).
DASHBOARD_VAR_QBITTORRENT_ENDPOINTqBittorrent WebUI endpoint, like https://127.0.0.1:9077.
DASHBOARD_VAR_QBITTORRENT_SERVERqBittorrent server address, like 127.0.0.1.
DASHBOARD_VAR_QBITTORRENT_API_KEYqBittorrent WebUI API key.
DASHBOARD_VAR_NEXTCLOUD_SERVERNextcloud server address, like 127.0.0.1.
DASHBOARD_VAR_NEXTCLOUD_ENDPOINTNextcloud server API endpoint, like https://127.0.0.1:8477.
DASHBOARD_VAR_NEXTCLOUD_TOKENNextcloud server API token.
DASHBOARD_VAR_NGINX_PROXY_MANAGER_ENDPOINTNginx Proxy Manager API endpoint, like https://127.0.0.1:8181.
DASHBOARD_VAR_NGINX_PROXY_MANAGER_SERVERNginx Proxy Manager server address, like 127.0.0.1.
DASHBOARD_VAR_NGINX_PROXY_MANAGER_USERNAMENginx Proxy Manager username.
DASHBOARD_VAR_NGINX_PROXY_MANAGER_PASSWORDNginx Proxy Manager password.
DASHBOARD_VAR_OPENWRT_ENDPOINTOpenWRT API endpoint, like https://192.168.1.1:8080. Also used as the r1-tpla9v6 machine's link/monitor URL in stack/homepage/config/services.yaml.
DASHBOARD_VAR_OPENWRT_SERVEROpenWRT server address, like 192.168.1.1.
DASHBOARD_VAR_OPENWRT_API_USERNAMEOpenWRT API username.
DASHBOARD_VAR_OPENWRT_API_PASSWORDOpenWRT API password.
MACHINE_OPENWRT_IPOpenWRT router address, like 192.168.1.1. Used as the "Only On Site" OpenWrt bookmark link in stack/glance/config/applications/bookmarks-blr-home.yml.
DASHBOARD_VAR_S3_CONSOLE_ENDPOINTS3 Console endpoint, like https://127.0.0.1:9545.
DASHBOARD_VAR_S3_SERVERS3 server address, like 127.0.0.1.
DASHBOARD_VAR_VAULT_ENDPOINTVault Secrets Manager endpoint, like https://127.0.0.1:9744.
DASHBOARD_VAR_VAULT_SERVERVault Secrets Manager server address, like 127.0.0.1.
DASHBOARD_VAR_CODE_SERVER_ENDPOINTCode Server endpoint, like http://127.0.0.1:7745.
DASHBOARD_VAR_CODE_SERVER_IPCode Server address, like 127.0.0.1.
DASHBOARD_VAR_VIKUJAN_ENDPOINTVikunja server endpoint, like http://127.0.0.1:5773. Also used to build the Feed page's Vikunja tasks widget API URL.
DASHBOARD_VAR_VIKUJAN_SERVERVikunja server address, like 127.0.0.1.
DASHBOARD_VAR_VIKUJAN_API_KEYVikunja API token used by the Feed page's Vikunja tasks widget.
GH_PROD_API_TOKENRead-only GitHub API token used by the Feed page's releases and GitHub notifications widgets.

Dashboard auth​

Consumed by stack/apache/entrypoint.sh to generate /usr/local/apache2/conf/.htpasswd at container startup, gating every dashboard path behind a single login.

VariableDescription
DASHBOARD_VAR_USERUsername for the Apache login gate.
DASHBOARD_VAR_PASSWORDPassword for the Apache login gate.

Glance cache​

Sourced from .env and referenced as ${DASHBOARD_GLANCE_CACHE_SEC}s by the cache property of Glance widgets that poll an external API or feed (Nginx Proxy Manager, Devops activity, GitHub notifications, Vikunja tasks, Hacker News, Lobsters, RSS, Reddit, Twitch, videos, releases).

VariableDescription
DASHBOARD_GLANCE_CACHE_SECDefault cache duration, in seconds, for cacheable Glance widgets.

Homepage refresh rate​

Sourced from .env and referenced as {{HOMEPAGE_VAR_REFRESH_RATE_MS}} by the refreshInterval property of glances widgets in stack/homepage/config/services.yaml and the refresh property of the resources widget in stack/homepage/config/widgets.yaml.

VariableDescription
HOMEPAGE_VAR_REFRESH_RATE_MSDefault pull rate, in milliseconds, for Homepage's live stats widgets.

Machines​

Each physical host has an _IP / _DOCKER_TLS_PORT / _GLANCES_PASSWORD set. These feed the Machines entries and glances widgets in stack/homepage/config/services.yaml, and the remote Docker socket targets in stack/homepage/config/docker.yaml. The machine display name (S1 Dev, S1 Home, RB5 M3, Sash M1) must stay in sync with the variable name when renaming or adding a machine. MACHINE_S1_DEV_IP also builds the "Open WebUI - Only On Site" bookmark link in stack/glance/config/applications/bookmarks-blr-home.yml.

VariableDescription
MACHINE_S1_DEV_IPIP address of the "S1 Dev" machine.
MACHINE_S1_DEV_DOCKER_TLS_PORTmTLS Docker daemon port on "S1 Dev".
MACHINE_S1_DEV_GLANCES_PASSWORDGlances widget password for "S1 Dev".
MACHINE_S1_HOME_IPIP address of the "S1 Home" machine.
MACHINE_S1_HOME_DOCKER_TLS_PORTmTLS Docker daemon port on "S1 Home".
MACHINE_S1_HOME_GLANCES_PASSWORDGlances widget password for "S1 Home".
MACHINE_RB5_M3_IPIP address of the "RB5 M3" machine.
MACHINE_RB5_M3_DOCKER_TLS_PORTmTLS Docker daemon port on "RB5 M3".
MACHINE_RB5_M3_GLANCES_PASSWORDGlances widget password for "RB5 M3".
MACHINE_SASH_M1_IPIP address of the "Sash M1" machine.
MACHINE_SASH_M1_DOCKER_TLS_PORTmTLS Docker daemon port on "Sash M1".
MACHINE_SASH_M1_GLANCES_PASSWORDGlances widget password for "Sash M1".

Docker TLS certificates​

The homepage-certs service decodes these into PEM files on the homepage-certs-data named volume, which is mounted into the homepage container at /app/config/certs/, where stack/homepage/config/docker.yaml references them (certs/docker_tls_*.pem) to authenticate to the remote machines' Docker daemons over mTLS.

VariableDescription
DOCKER_MACHINE_TLS_CA_BASE64Base64-encoded Docker mTLS CA certificate.
DOCKER_MACHINE_TLS_CERT_BASE64Base64-encoded Docker mTLS client certificate.
DOCKER_MACHINE_TLS_KEY_BASE64Base64-encoded Docker mTLS client key.