A self-hosted, configurable homepage/dashboard for home lab services — cards for each service or module, grouped and reorderable, with live output widgets (CPU/mem/disk, custom commands) and optional API integrations for common self-hosted apps (the *arr stack, nzbget, etc).
Built to be distributable: no service, machine, or credential is hardcoded anywhere in the app. Everything — services, machine targets, output-card commands, API connections, card order, and groups — is user-configured data, not code.
- Laravel 13
- Livewire 4 (single-file components) + Alpine.js (bundled with Livewire)
- Tailwind CSS v4 (via
@tailwindcss/vite) - Flux UI (free tier) for form/button components
- SQLite
- Docker + Traefik for local development
- Service cards that open the linked site on click, each with an optional icon — paste any image URL, or search recognized self-hosted apps (sonarr, plex, etc.) against the free homarr-labs/dashboard-icons index for a one-click suggestion
- Docker service discovery: save a scan target (name + host) in Settings, run a manual
scan against its Docker Engine API (or
docker psover SSH), and turn discovered containers into cards. Prefers a container's TraefikHost()label for the URL when present, so services with no host-published port are still discovered correctly. Falls back to the image's declaredEXPOSEport for--network hostcontainers, which never show a port mapping otherwise — and still surfaces them with a bare host URL (no port) when the image declares none at all, rather than dropping them - Manual custom links for anything discovery doesn't cover
- Editable "output" cards: user-defined shell commands (local or remote, e.g. via SSH),
run non-blockingly on each page load, rendering raw output (disk space, load, etc.).
If a command SSHes into a saved machine, that machine needs a key saved in Settings
first — saving one auto-syncs it to
storage/ssh/{machine-name}for the command to use (e.g.-i /var/www/html/storage/ssh/media); commands referencing a machine with no saved key will fail with a permission-denied error - API-connected cards for services with an API — Sonarr and Radarr show series/movies, missing, and queue counts; Prowlarr shows enabled indexers, grabs, and failures; Bazarr shows missing subtitle counts; NZBGet shows download speed, status, and remaining size. API key or username/password auth, whichever the service needs. Clicking the card opens the service, same as a link card. A generic fallback covers any other API with a plain reachability check
- Drag-and-drop card reordering
- Expandable/collapsible groups ("folders") of cards
Requires Docker and a web external Docker network with Traefik routing
*.dev.local.test domains to their containers.
docker compose up -d --build
docker compose run --rm vite npm install # first time onlySite: http://homie.dev.local.test Vite dev server (HMR): http://vite.homie.dev.local.test
Run from the host — these wrap docker exec into the homie-app container:
composer pint # code style (auto-fix)
composer phpstan # static analysis (level 6)
composer rector # modernization suggestions (dry-run only)
composer rector:apply # apply rector changes (review the diff first)
composer pest # run the test suiteCommands that write files inside the container should run as www-data (UID 1000,
matching the host user) to avoid root-owned files on the bind mount:
docker exec -u www-data homie-app <command>The composer script wrappers above already do this. If you run docker exec directly
as root and end up with permission errors editing files afterward, fix ownership with:
docker exec -u root homie-app chown -R 1000:1000 /var/www/htmlSingle-branch: main. This project doesn't use the master/local split — work happens
directly on main.