# 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)
#   Remote access (issue #6876): echo "HERMES_DASHBOARD_BIND=0.0.0.0" >> .env
#   and echo "API_SERVER_KEY=<16+ chars>" >> .env, then recreate the stack.
#   The WebUI reports "Hermes agent is not responding" when the gateway
#   listener is down — API_SERVER_KEY must be set for port 8642 to open.
#
# 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:
      # Bind to loopback by default. For remote access (issue #6876), set
      # HERMES_AGENT_BIND=0.0.0.0 in .env — the gateway requires
      # API_SERVER_KEY (>=16 chars) to open the listener at all.
      - "${HERMES_AGENT_BIND:-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}
      # Gateway API server (port 8642). The WebUI's Tasks/cron health probe
      # reaches this listener via HERMES_API_URL below. The agent only starts
      # the listener when API_SERVER_KEY is a usable value (>=16 chars); set
      # it in .env to enable the gateway API. Without a key the gateway daemon
      # still runs (messaging + cron) but port 8642 stays unbound and the
      # WebUI reports "Gateway endpoint not reachable".
      - API_SERVER_ENABLED=true
      - API_SERVER_HOST=0.0.0.0
      - API_SERVER_KEY=${API_SERVER_KEY:-}
      # 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:
      # Loopback-only by default (the dashboard runs --insecure). To reach the
      # dashboard from another machine (issue #6876), set
      # HERMES_DASHBOARD_BIND=0.0.0.0 in .env and put it behind auth / a
      # reverse proxy.
      - "${HERMES_DASHBOARD_BIND:-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
      # Let the WebUI health/status checks reach the gateway container over
      # the compose network. Cron listing is local, but scheduled ticking and
      # the Tasks/System gateway pill need a reachable gateway URL (#4483).
      - HERMES_API_URL=http://hermes-agent:8642
      # 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}
      # Forward the API server key so the WebUI's gateway health probe can
      # authenticate (must match the agent's API_SERVER_KEY above).
      - HERMES_WEBUI_GATEWAY_API_KEY=${API_SERVER_KEY:-}
      # Optional: enable password auth. Set HERMES_WEBUI_PASSWORD in .env
      # (required if you expose the port beyond 127.0.0.1).
      - HERMES_WEBUI_PASSWORD=${HERMES_WEBUI_PASSWORD:-}
      # 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).
      # 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:
