# Two-container Docker Compose: Hermes Agent + Hermes WebUI## QUICK START:# docker compose -f docker-compose.two-container.yml up -d# Open http://localhost:8787## This runs the agent and web UI in separate containers connected via shared# Docker volumes. The WebUI installs the agent's Python dependencies from the# shared agent source volume at startup.## WHEN TO USE THIS:# - You want isolation between the agent gateway and the WebUI# - You're already running hermes-agent in its own container# - You don't need the dashboard (use docker-compose.three-container.yml for that)## 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. If you need git/node/etc. on the# WebUI's filesystem, install them in the WebUI image — or use a single-# container setup where everything lives in one place.## NOTE ON VOLUMES:# This file uses named Docker volumes (hermes-home, hermes-agent-src) which# work out of the box on every Docker installation. If you prefer bind mounts# to share an existing host directory:## volumes:# hermes-home:# driver: local# driver_opts:# type: none# o: bind# device: /home/youruser/.hermes## When using bind mounts, BOTH containers must mount the same host path,# AND your host directory must be readable by UID 1000 (the default). Run:# id -u && id -g# to find your host UID/GID, then put them in a .env file:# echo "UID=$(id -u)" >> .env# echo "GID=$(id -g)" >> .envservices: hermes-agent: image: nousresearch/hermes-agent:latest container_name: hermes-agent command: gateway run ports: # Gateway API — exposed on localhost only. # Other containers on hermes-net reach it via http://hermes-agent:8642. # Remove 127.0.0.1: to expose on the host network (e.g. for remote clients). - "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. # The agent image's entrypoint already supports usermod remapping. - 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 networks: - hermes-net hermes-webui: image: ghcr.io/nesquena/hermes-webui:latest container_name: hermes-webui depends_on: - hermes-agent ports: - "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. # Override with: HERMES_WORKSPACE=/your/path docker compose up # ${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. # In two-container setups the WebUI auto-detects UID/GID from the shared # hermes-home volume, but you can override explicitly if needed (#668): # Run `id -u` and `id -g` to find your values. # On macOS, UIDs start at 501 — set these in a .env file: # echo "UID=$(id -u)" >> .env && echo "GID=$(id -g)" >> .env - WANTED_UID=${UID:-1000} - WANTED_GID=${GID:-1000} # 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-netnetworks: hermes-net: driver: bridgevolumes: # 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 down # docker volume rm <project>_hermes-agent-src # docker compose pull # docker compose up -d # # The full procedure (and why) is documented in docs/docker.md. hermes-home: hermes-agent-src:
# Two-container Docker Compose: Hermes Agent + Hermes WebUI
#
# QUICK START:
# docker compose -f docker-compose.two-container.yml up -d
# Open http://localhost:8787
#
# This runs the agent and web UI in separate containers connected via shared
# Docker volumes. The WebUI installs the agent's Python dependencies from the
# shared agent source volume at startup.
#
# WHEN TO USE THIS:
# - You want isolation between the agent gateway and the WebUI
# - You're already running hermes-agent in its own container
# - You don't need the dashboard (use docker-compose.three-container.yml for that)
#
# 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. If you need git/node/etc. on the
# WebUI's filesystem, install them in the WebUI image — or use a single-
# container setup where everything lives in one place.
#
# NOTE ON VOLUMES:
# This file uses named Docker volumes (hermes-home, hermes-agent-src) which
# work out of the box on every Docker installation. If you prefer bind mounts
# to share an existing host directory:
#
# volumes:
# hermes-home:
# driver: local
# driver_opts:
# type: none
# o: bind
# device: /home/youruser/.hermes
#
# When using bind mounts, BOTH containers must mount the same host path,
# AND your host directory must be readable by UID 1000 (the default). Run:
# id -u && id -g
# to find your host UID/GID, then put them in a .env file:
# echo "UID=$(id -u)" >> .env
# echo "GID=$(id -g)" >> .env
services:
hermes-agent:
image: nousresearch/hermes-agent:latest
container_name: hermes-agent
command: gateway run
ports:
# Gateway API — exposed on localhost only.
# Other containers on hermes-net reach it via http://hermes-agent:8642.
# Remove 127.0.0.1: to expose on the host network (e.g. for remote clients).
- "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.
# The agent image's entrypoint already supports usermod remapping.
- 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
networks:
- hermes-net
hermes-webui:
image: ghcr.io/nesquena/hermes-webui:latest
container_name: hermes-webui
depends_on:
- hermes-agent
ports:
- "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.
# Override with: HERMES_WORKSPACE=/your/path docker compose up
# ${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.
# In two-container setups the WebUI auto-detects UID/GID from the shared
# hermes-home volume, but you can override explicitly if needed (#668):
# Run `id -u` and `id -g` to find your values.
# On macOS, UIDs start at 501 — set these in a .env file:
# echo "UID=$(id -u)" >> .env && echo "GID=$(id -g)" >> .env
- WANTED_UID=${UID:-1000}
- WANTED_GID=${GID:-1000}
# 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 down
# docker volume rm <project>_hermes-agent-src
# docker compose pull
# docker compose up -d
#
# The full procedure (and why) is documented in docs/docker.md.
hermes-home:
hermes-agent-src: