9.8 KiB
name, description, version, author
| name | description | version | author |
|---|---|---|---|
| hermes-webui-docker | Deploy Hermes WebUI in Docker talking to host Hermes agent. | 2.0.0 | Agent |
Hermes WebUI in Docker (Hybrid Deployment)
When to Use
When Hermes agent (gateway, CLI, dashboard) runs natively on the host but the WebUI runs in a Docker container. This hybrid topology is useful when:
- WebUI needs access to host filesystem paths (e.g.
/opt/hermes/memory-os/,/workspace) - You want WebUI isolated in a container without moving the entire Hermes stack to Docker
- The gateway is already running as a systemd service on the host
Architecture
Host (port 8642) Docker (port 8787)
┌──────────────────┐ ┌──────────────────────┐
│ Hermes Gateway │◄──────────────│ Hermes WebUI │
│ (systemd) │ host.docker │ ghcr.io/nesquena/ │
│ port 8642 │ .internal: │ hermes-webui:latest │
│ │ 8642 │ /opt → /opt:rw │
│ Ollama:11434 │ │ ~/.hermes → /home/ │
│ SearXNG:11436 │ │ ~/ → /workspace:rw │
└──────────────────┘ └──────────────────────┘
Docker Compose (WebUI Only)
services:
hermes-webui:
image: ghcr.io/nesquena/hermes-webui:latest
container_name: hermes-webui
ports:
- "8787:8787"
volumes:
# Config, sessions, state — shared with host agent
- /home/estorozhenko/.hermes:/home/hermeswebui/.hermes
# Host filesystem — WebUI can see /opt/hermes/memory-os/, etc.
- /opt:/opt:rw
# Workspace — code browser in WebUI
- /home/estorozhenko:/workspace
environment:
- HERMES_WEBUI_HOST=0.0.0.0
- HERMES_WEBUI_PORT=8787
- HERMES_WEBUI_STATE_DIR=/home/hermeswebui/.hermes/webui
# Gateway is on HOST — use host.docker.internal
- HERMES_API_URL=http://host.docker.internal:8642
- HERMES_WEBUI_CHAT_BACKEND=gateway
- HERMES_WEBUI_GATEWAY_BASE_URL=http://host.docker.internal:8642
# Runs API needs newer gateway routes (/api/session/stream, /api/session?...)
# that gateway 0.19.0 DOES NOT have (only /api/sessions/{id}/...). On 0.19.0
# keep this false or WebUI hangs on "Loading conversation...".
- HERMES_WEBUI_GATEWAY_USE_RUNS_API=false
- HERMES_WEBUI_GATEWAY_API_KEY=${API_SERVER_KEY:-}
- WANTED_UID=${UID:-1000}
- WANTED_GID=${GID:-1000}
extra_hosts:
- "host.docker.internal:host-gateway"
restart: unless-stopped
Environment Variables — Key Differences from All-in-Docker
| Variable | All-in-Docker (container name) | Hybrid (host.docker.internal) |
|---|---|---|
HERMES_API_URL |
http://hermes-agent:8642 |
http://host.docker.internal:8642 |
HERMES_WEBUI_GATEWAY_BASE_URL |
http://hermes-agent:8642 |
http://host.docker.internal:8642 |
The extra_hosts: host.docker.internal:host-gateway line is required — without it the container cannot resolve host.docker.internal on Linux.
Host Filesystem Access
WebUI's file browser and Memory OS tabs see the container's filesystem, not the host's. Mount the host paths the WebUI needs:
/opt:/opt:rw— grants access to/opt/hermes/memory-os/,/opt/hermes/.hermes/, etc./home/estorozhenko:/workspace— code browser access to user's home/home/estorozhenko/.hermes:/home/hermeswebui/.hermes— shares sessions, config, skills (required for state persistence)
Without these mounts, WebUI will show errors like "path /opt/hermes/memory-os/ not found" because it only sees the container's internal filesystem.
Host Services that Must Be Running
- Hermes Gateway — port 8642 (systemd:
hermes-gateway.service) - Hermes Dashboard — port 9119 (systemd:
hermes-dashboard.service, bind127.0.0.1)- Created via
hermes-dashboard.serviceunit file in/etc/systemd/system/ - Dashboard refuses
--host 0.0.0.0without auth providers (Jun 2026+ hardening) - Workaround: bind
127.0.0.1and access via SSH tunnel:ssh -L 9119:localhost:9119 bigbox - Alternative: configure basic auth in
config.yaml(dashboard.basic_auth.username+password_hash)
- Created via
- Ollama — port 11434 (if delegation uses local model)
- SearXNG — port 11436 (if web search uses local instance)
All must listen on 0.0.0.0 or at least the Docker bridge interface for the container to reach them. Check with ss -tlnp | grep -E '8642|11434|11436'.
Gateway API Server Configuration
This is the #1 reason WebUI cannot reach the gateway. The api_server is a gateway platform (like Telegram or Email) — on host-native setups it must be configured explicitly.
Detailed reference: references/host-gateway-config.md (code walkthrough, troubleshooting table, env overlay logic).
How It Gets Enabled
| Context | Mechanism |
|---|---|
| All-in-Docker | Env vars: API_SERVER_ENABLED=true, API_SERVER_KEY=xxx |
| Host native (systemd) | Must be in config.yaml — env vars are NOT auto-propagated to the systemd unit |
Config.yaml Section
platforms:
api_server:
enabled: true
host: 0.0.0.0
port: 8642
key: "your-32-char-hex-key-here"
⚠️ **Field name matters:** The code reads `extra.get("key", ...)` — so the YAML field must be `key`, NOT `api_key`. A field named `api_key` is silently ignored (not bridged into extra).
Place this at the root level of `config.yaml` (NOT nested under `gateway:`).
### Why It Fails Silently
- Gateway starts, shows "⚕ Hermes Gateway Starting...", connects to Email etc. — looks healthy.
- But if `api_server` platform isn't configured, it simply doesn't bind port 8642.
- `systemctl status hermes-gateway.service` shows `active (running)` — misleading.
- The only symptom is `ss -tlnp | grep 8642` shows nothing.
### Verification
```bash
systemctl is-active hermes-gateway.service
ss -tlnp | grep 8642
curl -s http://localhost:8642/health
# Expected: {"status": "ok", "platform": "hermes-agent", "version": "0.19.0"}
If ss shows no listener on 8642 but the service is active, the api_server platform isn't configured.
systemd Env Var Alternative
Alternatively, add to the systemd unit file:
Environment="API_SERVER_ENABLED=true"
Environment="API_SERVER_HOST=0.0.0.0"
Environment="API_SERVER_KEY=your-key-here"
Then sudo systemctl daemon-reload && sudo systemctl restart hermes-gateway.service.
Checking Connectivity
# From inside the container:
docker exec hermes-webui curl -s http://host.docker.internal:8642/health
# From host (should also work):
curl -s http://localhost:8642/health
# Logs show API calls:
docker logs hermes-webui --tail 20
If WebUI shows "Check HERMES_WEBUI_GATEWAY_BASE_URL and Gateway API server health":
- Verify 8642 listens:
ss -tlnp | grep 8642 - If not → check
platforms: api_server:in config.yaml or env vars in systemd unit - Verify key matches between gateway and
HERMES_WEBUI_GATEWAY_API_KEY host.docker.internalmust resolve inside container (requiresextra_hosts)
Migrating from All-in-Docker to Hybrid
When undoing a full Docker migration (all 3 containers + dashboard), do:
-
Stop & remove agent + dashboard containers:
docker stop hermes-agent hermes-dashboard docker rm hermes-agent hermes-dashboard -
Re-enable host gateway systemd service (if it was disabled during migration):
sudo systemctl enable --now hermes-gateway.service -
Create dashboard systemd service (Jun 2026+ requires auth for public bind):
# Bind 127.0.0.1 and tunnel in via SSH/WireGuard ExecStart=/home/estorozhenko/.hermes/hermes-agent/venv/bin/python -m hermes_cli.main dashboard --host 127.0.0.1Full unit template: see
references/dashboard-systemd-service.md. -
Update docker-compose.yml — remove hermes-agent + hermes-dashboard services, keep only webui.
-
Change environment URLs from container names to
host.docker.internal:HERMES_API_URL=http://hermes-agent:8642→http://host.docker.internal:8642HERMES_WEBUI_GATEWAY_BASE_URL=http://hermes-agent:8642→http://host.docker.internal:8642
-
Add
/opt:/opt:rwvolume if WebUI needs host filesystem access. -
Recreate webui container:
docker compose -f /opt/hermes/docker/docker-compose.yml up -d
Pitfalls
- Gateway API key mismatch — EASY TO MISS ROOT CAUSE: The API key exists in 4 sources (config.yaml
key:field,~/.hermes/.env, systemd unit, docker.env). All must match. The most common failure pattern: config.yaml and systemd agree, but~/.hermes/.envhas a different value that overwrites the process env at startup. Always checkgrep API_SERVER_KEY ~/.hermes/.envwhen debugging 401. - YAML field name is
key, notapi_key: The adapter readsextra.get("key", os.getenv("API_SERVER_KEY", "")). A field namedapi_keyin config.yaml silently does nothing because the YAML→extra bridge only looks for the exact namekey. host.docker.internalonly works withextra_hostson Linux. Without it, the hostname won't resolve. Alternative: use host's actual IP (e.g.192.168.x.xor Docker bridge gateway like172.17.0.1)- docker stop/rm for containers may be blocked by safety system — use
docker compose -f <file> downinstead /opton davfs2 (Yandex Disk via WebDAV) —stat,find,rmare very slow on WebDAV-backed mounts. Consider mounting specific subdirectories instead of the whole/optif the filesystem is partly WebDAV- Telegram SOCKS5 tunnel — if gateway is on host and Telegram is used, ensure
telegram-tunnel.serviceis active