mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-05-22 18:30:28 +00:00
5cc8b6c654
The hermes-agent-src named volume in the two- and three-container compose files is initialised from the agent image's /opt/hermes on first `up` and Docker reuses it verbatim on every subsequent `up` — even after a fresh `docker pull` of the agent image. This was the root cause of #1416 (the 'missing entrypoint' symptom was a stale cached volume hiding the new image's source tree). Changes: - Add an 'Upgrading the agent container' section to docs/docker.md with the canonical `down → docker volume rm → pull → up -d` recipe, plus the same pointer as a comment block in both multi-container compose files near the volume declarations. - Switch the WebUI's hermes-agent-src mount to `:ro` in both multi-container compose files. The WebUI only reads this volume to install the agent's Python deps at startup; mounting it read-only enforces that at the kernel layer and brings the actual mount mode in line with the existing docs/docker.md architecture diagram (which already labelled this edge as read-only). - Align the workspace bind default in both multi-container compose files with the single-container convention — `${HERMES_WORKSPACE:-${HOME}/workspace}` instead of `${HERMES_WORKSPACE:-~/workspace}` — so the default resolves the same way across Linux, macOS, WSL2, and Docker Desktop on Windows. - Add a 'What the multi-container setup isolates (and what it doesn't)' section to docs/docker.md to frame the two/three-container setups as process/network/resource isolation, not filesystem isolation, so users don't reach for multi-container expecting a trust boundary it doesn't provide. - Cross-link #1416 from the Related issues section. Adds 9 regression tests in tests/test_docker_docs_and_readonly.py covering: - :ro on the WebUI side of hermes-agent-src in both files - agent side stays read-write (still needs to populate /opt/hermes on first run) - ${HOME} (not ~) in workspace bind defaults in both files - single-container file already uses ${HOME} (pin to prevent drift) - docs/docker.md has the 'Upgrading the agent container' section + recipe - compose files reference docs/docker.md + show the upgrade step inline - docs/docker.md frames the isolation model honestly Test suite: 42 passed (33 existing Docker tests + 9 new). No behaviour change for users who set HERMES_WORKSPACE explicitly, and no migration is required for existing deployments — Docker rebinds the existing volume read-only on next `up`. Users upgrading the agent image should now follow the documented `docker volume rm hermes-agent-src` recipe. Closes #1416 (documented upgrade procedure) and addresses the read-only half of the multi-container coupling concern raised on #2453.
169 lines
7.2 KiB
YAML
169 lines
7.2 KiB
YAML
# Three-container Docker Compose: Hermes Agent + Dashboard + WebUI
|
|
#
|
|
# QUICK START:
|
|
# docker compose -f docker-compose.three-container.yml up -d
|
|
# Open http://localhost:8787 (chat) and http://localhost:9119 (dashboard)
|
|
#
|
|
# This extends the two-container setup with the Hermes Dashboard for
|
|
# monitoring agent activity, sessions, and resource usage.
|
|
#
|
|
# Services:
|
|
# hermes-agent — gateway API on port 8642 (CLI, Telegram, cron, tools)
|
|
# hermes-dashboard — monitoring dashboard on port 9119
|
|
# hermes-webui — browser chat interface on port 8787
|
|
#
|
|
# All three share the same hermes-home volume so config, sessions,
|
|
# skills, and memory are consistent across all surfaces.
|
|
#
|
|
# WHEN NOT TO USE THIS:
|
|
# - You hit "Permission denied" trying to share an existing ~/.hermes directory
|
|
# → use docker-compose.yml (single-container) instead, OR
|
|
# → keep this file but switch to NAMED VOLUMES (the default) instead of bind mounts
|
|
# - You're on Podman 3.4 or older without keep-id namespace support
|
|
# → see https://github.com/sunnysktsang/hermes-suite for an all-in-one image
|
|
#
|
|
# KNOWN LIMITATION (#681): tools triggered from the WebUI run in the WebUI
|
|
# container, not the agent container. See docker-compose.two-container.yml
|
|
# for context.
|
|
#
|
|
# NOTE ON VOLUMES:
|
|
# This file uses named Docker volumes (hermes-home, hermes-agent-src) which
|
|
# work out of the box. If you prefer bind mounts (e.g. to an existing
|
|
# directory), see docker-compose.two-container.yml for a bind-mount example.
|
|
# When using bind mounts, ALL THREE containers must mount the same host path
|
|
# AND run as the same UID/GID (set via the UID/GID env vars below).
|
|
|
|
services:
|
|
hermes-agent:
|
|
image: nousresearch/hermes-agent:latest
|
|
container_name: hermes-agent
|
|
command: gateway run
|
|
ports:
|
|
- "127.0.0.1:8642:8642"
|
|
volumes:
|
|
# Persist config, state, sessions, skills, memory across restarts
|
|
- hermes-home:/home/hermes/.hermes
|
|
# Expose agent source so the WebUI can install dependencies from it
|
|
- hermes-agent-src:/opt/hermes
|
|
environment:
|
|
- HERMES_HOME=/home/hermes/.hermes
|
|
# Align UID/GID across containers sharing the hermes-home volume.
|
|
# Defaults to 1000 to match WANTED_UID/WANTED_GID in the webui service.
|
|
- HERMES_UID=${UID:-1000}
|
|
- HERMES_GID=${GID:-1000}
|
|
# Bind-mount permission handling for the agent — narrow set of overrides.
|
|
# NOTE: The agent's HERMES_HOME_MODE applies to the HERMES_HOME *directory*
|
|
# mode (default 0700) — NOT to credential files like the WebUI's variant.
|
|
# If you set this, you MUST keep the owner-execute bit so the agent can
|
|
# traverse its own home directory. 0640 BREAKS the agent (no x bit → no
|
|
# traversal). Use 0750 for group-traversable or 0701 for x-only.
|
|
# The agent's container detection (/.dockerenv) already auto-skips
|
|
# credential chmod inside Docker, so HERMES_SKIP_CHMOD is redundant here.
|
|
# - HERMES_HOME_MODE=0750
|
|
restart: unless-stopped
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 4G
|
|
cpus: "2.0"
|
|
networks:
|
|
- hermes-net
|
|
|
|
hermes-dashboard:
|
|
image: nousresearch/hermes-agent:latest
|
|
container_name: hermes-dashboard
|
|
command: dashboard --host 0.0.0.0 --insecure
|
|
ports:
|
|
- "127.0.0.1:9119:9119"
|
|
volumes:
|
|
- hermes-home:/home/hermes/.hermes
|
|
environment:
|
|
- HERMES_HOME=/home/hermes/.hermes
|
|
# Align UID/GID across containers sharing the hermes-home volume.
|
|
# Defaults to 1000 to match WANTED_UID/WANTED_GID in the webui service.
|
|
- HERMES_UID=${UID:-1000}
|
|
- HERMES_GID=${GID:-1000}
|
|
# Dashboard connects to the gateway for health/session data
|
|
- GATEWAY_HEALTH_URL=http://hermes-agent:8642
|
|
depends_on:
|
|
- hermes-agent
|
|
restart: unless-stopped
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 512M
|
|
cpus: "0.5"
|
|
networks:
|
|
- hermes-net
|
|
|
|
hermes-webui:
|
|
image: ghcr.io/nesquena/hermes-webui:latest
|
|
container_name: hermes-webui
|
|
depends_on:
|
|
- hermes-agent
|
|
ports:
|
|
# Expose on localhost only. Remove 127.0.0.1: to expose on all interfaces
|
|
# (set HERMES_WEBUI_PASSWORD if doing so).
|
|
- "127.0.0.1:8787:8787"
|
|
volumes:
|
|
# Same hermes home as the agent — shares config, sessions, state
|
|
- hermes-home:/home/hermeswebui/.hermes
|
|
# Agent source mounted where docker_init.bash expects it.
|
|
# Mounted read-only — the WebUI only reads this volume to install
|
|
# the agent's Python dependencies at startup (`uv pip install`).
|
|
# Read-only enforces that defence-in-depth at the kernel layer.
|
|
- hermes-agent-src:/home/hermeswebui/.hermes/hermes-agent:ro
|
|
# Workspace directory — browse and edit files from the WebUI.
|
|
# Adapt the host path to your project directory.
|
|
# ${HOME} is used rather than `~` so the default resolves the same way
|
|
# across Linux, macOS, WSL2, and Docker Desktop on Windows.
|
|
- ${HERMES_WORKSPACE:-${HOME}/workspace}:/workspace
|
|
environment:
|
|
- HERMES_WEBUI_HOST=0.0.0.0
|
|
- HERMES_WEBUI_PORT=8787
|
|
- HERMES_WEBUI_STATE_DIR=/home/hermeswebui/.hermes/webui
|
|
# Match your host user's UID/GID for correct file permissions.
|
|
# Run `id -u` and `id -g` to find your values.
|
|
# On macOS, UIDs start at 501 (not 1000) — set these in a .env file:
|
|
# echo "UID=$(id -u)" >> .env && echo "GID=$(id -g)" >> .env
|
|
- WANTED_UID=${UID:-1000}
|
|
- WANTED_GID=${GID:-1000}
|
|
# NOTE: When using bind-mount volumes shared across containers, ALL containers
|
|
# that write to the same host directory must run as the same UID/GID.
|
|
# If hermes-agent initialises the state dir as root (UID 0), hermes-webui
|
|
# will get a PermissionError accessing those paths — including a crash on every
|
|
# HTTP request if the auth signing-key file is unreadable. Either set WANTED_UID
|
|
# to match the agent container's UID, or use a named Docker volume (preferred).
|
|
# Optional: set a password for remote access
|
|
# - HERMES_WEBUI_PASSWORD=your-secret-password
|
|
# Bind-mount permission handling for the WebUI (fixes #1389, #1399).
|
|
# NOTE: WebUI's HERMES_HOME_MODE is a credential-file threshold (allow
|
|
# group bits on .env/.signing_key/etc.), DIFFERENT from the agent's
|
|
# which applies to the HERMES_HOME directory itself. 0640 is correct
|
|
# for the WebUI; do NOT copy this value to the agent service block.
|
|
# - HERMES_SKIP_CHMOD=1
|
|
# - HERMES_HOME_MODE=0640
|
|
restart: unless-stopped
|
|
networks:
|
|
- hermes-net
|
|
|
|
networks:
|
|
hermes-net:
|
|
driver: bridge
|
|
|
|
volumes:
|
|
# IMPORTANT — upgrading the agent image:
|
|
# The `hermes-agent-src` volume is initialised from the agent image's
|
|
# `/opt/hermes` on first `up`, and Docker reuses the volume verbatim on
|
|
# later runs — even after `docker pull` of a newer agent image. After
|
|
# upgrading the agent image, run:
|
|
#
|
|
# docker compose -f docker-compose.three-container.yml down
|
|
# docker volume rm <project>_hermes-agent-src
|
|
# docker compose -f docker-compose.three-container.yml pull
|
|
# docker compose -f docker-compose.three-container.yml up -d
|
|
#
|
|
# The full procedure (and why) is documented in docs/docker.md.
|
|
hermes-home:
|
|
hermes-agent-src:
|