Files
2026-09-06 13:50:57 +00:00

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, bind 127.0.0.1)
    • Created via hermes-dashboard.service unit file in /etc/systemd/system/
    • Dashboard refuses --host 0.0.0.0 without auth providers (Jun 2026+ hardening)
    • Workaround: bind 127.0.0.1 and access via SSH tunnel: ssh -L 9119:localhost:9119 bigbox
    • Alternative: configure basic auth in config.yaml (dashboard.basic_auth.username + password_hash)
  • 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":

  1. Verify 8642 listens: ss -tlnp | grep 8642
  2. If not → check platforms: api_server: in config.yaml or env vars in systemd unit
  3. Verify key matches between gateway and HERMES_WEBUI_GATEWAY_API_KEY
  4. host.docker.internal must resolve inside container (requires extra_hosts)

Migrating from All-in-Docker to Hybrid

When undoing a full Docker migration (all 3 containers + dashboard), do:

  1. Stop & remove agent + dashboard containers:

    docker stop hermes-agent hermes-dashboard
    docker rm hermes-agent hermes-dashboard
    
  2. Re-enable host gateway systemd service (if it was disabled during migration):

    sudo systemctl enable --now hermes-gateway.service
    
  3. 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.1
    

    Full unit template: see references/dashboard-systemd-service.md.

  4. Update docker-compose.yml — remove hermes-agent + hermes-dashboard services, keep only webui.

  5. Change environment URLs from container names to 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
  6. Add /opt:/opt:rw volume if WebUI needs host filesystem access.

  7. 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/.env has a different value that overwrites the process env at startup. Always check grep API_SERVER_KEY ~/.hermes/.env when debugging 401.
  • YAML field name is key, not api_key: The adapter reads extra.get("key", os.getenv("API_SERVER_KEY", "")). A field named api_key in config.yaml silently does nothing because the YAML→extra bridge only looks for the exact name key.
  • host.docker.internal only works with extra_hosts on Linux. Without it, the hostname won't resolve. Alternative: use host's actual IP (e.g. 192.168.x.x or Docker bridge gateway like 172.17.0.1)
  • docker stop/rm for containers may be blocked by safety system — use docker compose -f <file> down instead
  • /opt on davfs2 (Yandex Disk via WebDAV) — stat, find, rm are very slow on WebDAV-backed mounts. Consider mounting specific subdirectories instead of the whole /opt if the filesystem is partly WebDAV
  • Telegram SOCKS5 tunnel — if gateway is on host and Telegram is used, ensure telegram-tunnel.service is active