The Problem
Homelab dashboards like Homarr and Homepage are feature-rich but heavy. Hestia targets the gap between static bookmark pages and full dashboard frameworks: a browser-only, grid-based dashboard with pluggable API integrations for homelab services (Glances, Pi-hole, Deluge, Jellyfin), persisted entirely in the browser with no backend.
What This Does
A vanilla JavaScript dashboard. 53 JS files, 7 CSS files, one HTML entry point. Apps are classes extending BaseApp (js/apps/baseApp.js) registered through js/registry.js; each implements render and onMount. The grid system (js/grid.js, js/grid/virtualGrid.js) handles drag/drop layout with View Transitions API animations. Theming is Base16-based via js/ui/theme.js.
Persistence is localStorage through js/storage.js (saveState, loadState, exportStateToFile, importStateFromFile). Images go to IndexedDB via js/imageStore.js. The Dockerfile ships nginx with default.conf proxying API calls to avoid CORS.
How It Is Wired
Execution starts at index.html loading js/main.js, which imports 14 modules and boots the registry. The architecture is hub-and-spoke: js/registry.js has 17 modules importing it and imports nothing (instability 0) — it's the stable core. js/dom.js (Ca 11) and js/apps/baseApp.js (Ca 15) are the other stable hubs. On the unstable side, js/apps/appIndex.js (Ca 1, Ce 15, instability 0.94) and js/main.js (Ce 14, instability 1) are thin composition layers — high churn, low blast radius.
The call graph shows 209 internal edges. createEl (js/dom.js) is called from 14 places — the widest blast radius; change its signature and most apps break. saveState (js/storage.js) is called from 9 places, showToast (js/ui/toasts.js) from 8. Network effects go through safeFetch/fetchGlances (js/apps/glances/gCore.js) hitting proxied endpoints. State flows through js/state.js (subscribe/notify/setState/getState), a tiny pub/sub. No circular dependencies; 0 modules in cycles.
File map: js/utils.js owns helpers (generateId, resolveToHex, toPx), js/grid.js owns layout rendering, js/storage.js owns all persistence, js/ui/* owns modal/toast/theme/editor UI, js/apps/* owns one integration each.
How To Use It
git clone https://github.com/moses-y/hestia-core
cd hestia-core
docker build -t hestia-core .
docker run -d -p 8080:80 --name hestia hestia-core
Or serve statically: python3 -m http.server 8000. Before building, edit default.conf to point proxy paths at your real server IPs. In the dashboard UI, configure apps with relative paths: /pi-api/admin/api.php, /deluge-api/json, /jellyfin-api/. No env vars or API keys required; the weather app uses Open-Meteo directly.
Real-World Use
A homelab operator runs Hestia in Docker on a small server. The Glances app (js/apps/glancesApp.js + js/apps/glances/gCore.js) polls the local Glances API every few seconds, drawing CPU/mem/network graphs. Pi-hole and Deluge apps show query counts and torrent speeds. The nginx proxy in default.conf handles CORS so the browser never hits cross-origin blocks. Layout and theme export to JSON via the settings panel for backup.
Code Health & Issues
Static analysis (measured, not opinion) found 32 findings: 2 high, 30 medium, 0 low.
- High — Deep nesting x19 — js/grid/virtualGrid.js, js/apps/rssApp.js, js/ui/colorPicker.js (max indentation depth 11). Fix: early returns/guard clauses.
- Medium — High branching density x8 — js/dom.js, js/utils.js, js/grid.js (25 branch points over 83 lines).
- Medium — Hub modules x2 — js/registry.js, js/apps/baseApp.js (17 and 15 dependents; high blast radius).
- Medium — Empty catch blocks x2 — js/apps/fetchApp.js, js/apps/rssApp.js (silently discard errors).
- Medium — Duplicated code — 7 repeated 6-line blocks across js/apps/glances/gCpu.js, gMem.js, gNetwork.js, js/ui/settingsPanel.js.
SDLC: no test suite (53 source files, 0 test files), no CI workflow, Dockerfile uses unpinned nginx:alpine base and no non-root USER. Licence present.
The Bottom Line
Clean hub-and-spoke architecture with no circular dependencies, and the stable hubs (registry, dom, baseApp) are genuinely small. The gaps are process, not design: no tests, no CI, and a Dockerfile that needs hardening. Good fit for homelab users who want a hackable, self-contained dashboard; less so for teams needing regression safety.