Installation
Install Homelab Hand with Docker, run it behind HTTPS, and configure it.
Homelab Hand runs as a single Docker container with an SQLite database inside a Docker volume. You can try it on your laptop in a minute, then install it on a server that can reach the machines you want to manage.
Requirements
| What | Details |
|---|---|
| Docker (recommended) | Docker Engine 24 or later with the Compose plugin, or Docker Desktop on macOS / Windows. |
| Without Docker | Python 3.10 or later. The included run-without-docker.sh sets up a virtualenv for you. |
| Hardware | Anything that runs Docker. The hub typically uses 150–250 MB of RAM (about 140 MB on a lab with every module on). A small VM or LXC, a Raspberry Pi 4/5 or a NAS is plenty. |
| Network: hub → your machines | SSH (22), Docker API (2376), Hawser (2376), Proxmox (8006), Synology DSM (5000/5001), TrueNAS (443) and the UniFi console (443). Only what you use. |
| Network: agents → hub | Edge agents and Hawser Edge dial out to the hub over HTTPS/WSS, so those hosts need to reach the hub's address. |
Where to install it? Pick a machine that's always on and on your main LAN, such as a small Proxmox LXC or a Docker VM. The hub has to reach everything it manages. The Homelab Hand agent covers hosts it can't reach, like a VPS or a machine behind NAT.
Option 1: try it locally
From the project folder on a machine with Docker:
./start.sh
The script:
- creates
.envwith a randomHUB_SECRET_KEY; - builds the image and starts the hub at http://localhost:8080 (use
HUB_PORT=9090 ./start.shto pick another port); - mounts your Docker socket and adds it as an environment named after your machine, so there's something to look at straight away.
Stop it with ./stop.sh. To wipe the test data completely:
docker compose -f docker-compose.local.yml down -v
Without Docker, ./run-without-docker.sh creates .venv, installs the dependencies and keeps data in ./data.
Option 2: install on a server
- Copy the project to the serverfor example to
/opt/htech-hub. - Create the configuration
Then setcd /opt/htech-hub cp .env.example .env openssl rand -hex 32 # paste the output into .env as HUB_SECRET_KEYHUB_ORIGIN(the address you'll browse to) andHUB_RP_ID(just its hostname). See environment variables. - Start it
docker compose up -d --build - Optional: manage this server's own Dockeruncomment the Docker socket mount and
group_addindocker-compose.yml, then rundocker compose up -dagain. - Open itand create the first admin account (see First steps).
Back up HUB_SECRET_KEY. It encrypts every stored password, SSH key and API token. If you lose it, those credentials can't be decrypted and you'll have to enter them all again. Keep a copy in your password manager.
Install and update with deploy.sh
From your workstation, deploy.sh does all of the above over SSH and handles every later update with the same command:
DEPLOY_HOST=root@192.168.1.20 ./deploy.sh
It copies the code, creates .env with a secret on the first run only, writes a server compose file (port 8756, Docker socket, auto-registered local environment), builds, starts and waits for the health check. The update swaps the new code in atomically. See Operations for the one-time key setup and options.
HTTPS and reverse proxy
Use HTTPS for anything beyond a quick test. Passkeys require it, and it protects your session cookie, agent tokens and terminal traffic. Any reverse proxy works (Nginx Proxy Manager, Traefik, Caddy or a Cloudflare Tunnel) as long as it:
- forwards WebSockets (terminals, agents, Hawser Edge, live camera video, the live map);
- passes
X-Forwarded-ForandX-Forwarded-Proto; - doesn't buffer long responses (log tails and job output stream).
Caddy example:
hub.example.com {
reverse_proxy hub:8080
}
Then set, in .env:
HUB_ORIGIN=https://hub.example.com
HUB_RP_ID=hub.example.com
HUB_TRUSTED_PROXIES=172.18.0.5 # your proxy / tunnel connector's IP
HUB_TRUSTED_PROXIES matters. Only those addresses may set X-Forwarded-For. Without it, the hub sees every visitor as your proxy, and the login rate limit applies to all of them together.
Using Cloudflare? Follow Cloudflare Tunnel and Cloudflare Access. The iOS app supports Access service tokens.
Environment variables
| Variable | Default | Purpose |
|---|---|---|
HUB_SECRET_KEY | required | Encrypts stored credentials (at least 32 characters). Back it up. |
HUB_ORIGIN | http://localhost:8080 | The URL you browse to. Used for passkeys, agent install commands, links in notifications and cookie security. |
HUB_RP_ID | localhost | Passkey (WebAuthn) relying-party ID: the bare hostname of HUB_ORIGIN. |
HUB_TRUSTED_PROXIES | 127.0.0.1 | Reverse proxy / tunnel IPs or CIDRs whose X-Forwarded-For is trusted, comma-separated. |
HUB_RP_NAME | Homelab Hand | Name shown in authenticator apps and passkey prompts. |
HUB_COOKIE_SECURE | on for https | Mark the session cookie Secure. |
HUB_SESSION_HOURS | 12 | How long a browser sign-in lasts. |
HUB_DATA_DIR | /data | Database, icon cache, backups and certificates. |
HUB_BACKUP_DIR | /data/backups | Where container config and data backups are stored. |
HUB_AUTO_LOCAL | off | 1 registers the mounted Docker socket as an environment at start-up. |
HUB_DOCKER_SOCKET | /var/run/docker.sock | Socket used by HUB_AUTO_LOCAL. |
HUB_HELPER_IMAGE | alpine:3.20 | Image for the opt-in privileged helper (OS updates / host shell over the Docker API). |
HUB_COMPOSE_IMAGE | docker:cli | Image for the compose helper (stacks and updates on socket/API environments). |
HUB_PUBLIC_URL | – | The public https address, used by deploy.sh and by Google/Apple sign-in (also settable in the UI). |
In-app settings
Most behaviour is set in the UI, under Alerts & Activity → Alert settings and Settings → General:
| Setting | Default | What it does |
|---|---|---|
| Container poll interval | 30 s | How often container state and stats refresh. |
| Integration poll interval | 60 s | Proxmox, Synology, UniFi, CrowdSec and app integrations. This also drives map traffic. |
| OS update check | every 6 h | Package checks on hosts with a command channel. |
| Image update check | every 12 h | Registry digest comparison (0 = off). |
| CPU / memory alert | 90 % | Must last two polls in a row. |
| NAS temperature alert | 60 °C | Synology and TrueNAS disks and systems. |
| Alert cooldown | 30 min | The same alert isn't re-sent within this window. |
| Muted containers | – | Glob patterns that never alert, e.g. backup-*. |
| Alert on unexpected stop | on | Stops done through the hub never alert. |
| MCP autonomy | act | approve queues AI tool calls from MCP for a human. |
| AI may run host commands | off | Enables the run_host_command AI tool. |
| Stacks directory | /opt/stacks | Where new stacks are written on hosts. |
| Icon overrides | – | Map names to dashboard-icons slugs or image URLs. |
Updating
With deploy.sh, run the same command again. Otherwise pull or copy the new code and run docker compose up -d --build. Database changes are applied automatically at start-up. Downgrades aren't supported, so take a configuration backup before big jumps.