This page provides a detailed technical overview of the Docker-based deployment for the Hermes Agent. It documents the image build configuration via the Dockerfile, the use of the s6-overlay init system for process supervision, the bootstrap mechanism implemented in stage2-hook.sh, the container lifecycle management (including UID remapping and profile reconciliation), and the internal service management interface exposed by hermes_cli/service_manager.py. The docker-compose.yml setup is also briefly discussed.
The Hermes Agent Docker image employs the s6-overlay init system which replaces simpler tools like tini to supervise multiple internal services with robust restart and signal forwarding semantics. This enables long-running processes such as per-profile messaging gateways and the web dashboard to be monitored and restarted automatically within the container Dockerfile22-28
Legacy compatibility is maintained via a shim script (docker/tini-shim.sh) that satisfies orchestration templates still expecting /usr/bin/tini but now forwards to /init (the s6-svscan init process) without breaking the supervision tree Dockerfile80-89
/init, the s6-svscan process Dockerfile28s6-svscan launches the main-wrapper.sh shell script, which launches the Hermes CLI or other maintenance bootstrap commands docker/main-wrapper.sh1-10s6-overlay dynamically manages services defined as directories under /run/service/.docker/s6-rc.d/ which s6-rc uses to supervise the dashboard, gateways, and other components docker/s6-rc.d/dashboard/run docker/s6-rc.d/gateway/runSources: Dockerfile22-28 Dockerfile80-89 docker/main-wrapper.sh1-10 hermes_cli/service_manager.py54-82 hermes_cli/service_manager.py86-101 docker/s6-rc.d/dashboard/run docker/s6-rc.d/gateway/run
docker/stage2-hook.sh)The stage2-hook.sh script runs under s6-overlay’s cont-init.d phase right after the container startup but before user services launch docker/cont-init.d/02-reconcile-profiles It executes as root and sets up the container environment:
UID/GID Remapping: Aligns the internal hermes user UID and GID with the (HERMES_UID / HERMES_GID) environment variables, or their NAS aliases (PUID / PGID). This allows file permissions on the mounted volume (/opt/data) to match the host user, crucial for persistence and avoiding permission issues docker/stage2-hook.sh97-116
Rejects Unsupported docker run --user: Starting the container with a pinned arbitrary UID via --user breaks root-required bootstrap logic and is explicitly forbidden to prevent failures docker/stage2-hook.sh26-74
Docker Socket Group Fix: When the host's Docker socket is bind-mounted inside the container, the hermes user is added to the socket's group dynamically by injecting group entries into /etc/group, enabling proper DooD (Docker-outside-of-Docker) execution from inside the container docker/stage2-hook.sh117-139
Volume Initialization & Profile Seeding: Ensures the persistent /opt/data volume structure exists with the necessary directory layout and copies essential seed data for newly created profiles, as well as syncing bundled and optional skills as needed so profiles are ready on first use docker/stage2-hook.sh76-86
Privilege Drop: While this script executes as root, the actual service processes drop privileges to the hermes user at runtime using s6-setuidgid, ensuring minimal permissions for running services docker/stage2-hook.sh24
hermes_cli/container_boot.py)The /run/service/ directory where s6 manages services lives in tmpfs and is wiped on container restart. Hermes uses the container_boot.py module to reconcile the persistent profile gateways stored under /opt/data/profiles/ with the ephemeral /run/service/ directory on every container boot hermes_cli/container_boot.py7-19
It scans all profiles on disk and recreates a supervised slot for each profile's gateway service directory (gateway-<profile>/) hermes_cli/container_boot.py127-128
Only those profiles whose last recorded desired_state was "running" are automatically restarted; states like "stopped" or "startup_failed" leave the slot registered but not started, avoiding crash loops hermes_cli/container_boot.py35-39
Transient runtime states like "draining" or "degraded" are mapped back to "running" for autostart purposes to handle edge cases of unexpected termination during shutdown hermes_cli/container_boot.py41-69
Sources: docker/stage2-hook.sh24 docker/stage2-hook.sh26-74 docker/stage2-hook.sh76-86 docker/stage2-hook.sh97-116 docker/stage2-hook.sh117-139 docker/cont-init.d/02-reconcile-profiles hermes_cli/container_boot.py7-19 hermes_cli/container_boot.py35-39 hermes_cli/container_boot.py41-69 hermes_cli/container_boot.py127-128
hermes_cli/service_manager.pyTo abstract over different init systems, Hermes defines a ServiceManager protocol interface with implementations for systemd, launchd, Windows Scheduled Tasks, and s6 hermes_cli/service_manager.py54-82
Inside the container, Hermes detects if it is running under s6 by:
/proc/1/comm and verifying the process name is s6-svscan./run/s6/basedir exists hermes_cli/service_manager.py129-160This approach supports both root and unprivileged users (UID 10000 hermes) even when /proc/1/exe is unreadable hermes_cli/service_manager.py138-142
The s6 backend supports runtime registration of new profile gateway services dynamically at container runtime:
/run/service/gateway-<profile> containing the service run script.s6-svscan is notified (s6-scan or similar) to monitor the new directory and starts the gateway process running under the hermes user via s6-setuidgid.Sources: hermes_cli/service_manager.py33-50 hermes_cli/service_manager.py54-82 hermes_cli/service_manager.py67-83 hermes_cli/service_manager.py129-160 hermes_cli/service_manager.py138-142
The image exposed environment variables to control startup and runtime behavior include:
| Environment Variable | Description | Default |
|---|---|---|
HERMES_UID, PUID | User ID inside container to remap the hermes user to match host volume ownership. | 10000 |
HERMES_GID, PGID | Group ID inside container for similar remapping. | 10000 |
HERMES_DASHBOARD | Enable the web dashboard service when set to 1. | (unset, dashboard disabled) |
PLAYWRIGHT_BROWSERS_PATH | Location for Playwright browser binaries to survive volume overlay. | /opt/hermes/.playwright Dockerfile20 |
PYTHONUNBUFFERED | Unbuffered Python stdout/stderr for real-time logs in docker logs. | 1 Dockerfile15 |
PYTHONDONTWRITEBYTECODE | Prevent writing .pyc files in the immutable filesystem paths. | 1 Dockerfile16 |
Dockerfile)The Dockerfile uses a multi-stage approach:
uv_source stage: Provides the uv dependency resolver binary for Python, pinned at Python 3.13 Dockerfile1-4node_source stage: Copies Node.js 22 LTS binaries and accompanying npm and corepack from the official node:22-bookworm-slim image, choosing this version to stay aligned with Debian trixie (glibc 2.41) compatibility and long-term support Dockerfile5-9ripgrep, ffmpeg, Docker CLI, Python 3) and unpacks the s6-overlay launch system tarballs specific to the container's architecture Dockerfile10-76A docker-compose.yml file describes running a Hermes Agent container:
~/.hermes or equivalent to /opt/data in the container for persistent state docker-compose.yml68642) and optionally the dashboard (9119) docker-compose.yml7-8Sources: Dockerfile1-4 Dockerfile5-9 Dockerfile10-76 Dockerfile15 Dockerfile16 Dockerfile20 Dockerfile90-313 docker-compose.yml6 docker-compose.yml7-8 docker-compose.yml10
The Docker deployment of Hermes Agent is engineered for production reliability and flexibility with:
s6-overlay for robust init supervision replacing tini.stage2-hook.sh script for UID remapping, volume chown, and configuration seeding.These components together ensure Hermes runs predictably, isolated, and highly available in containerized environments.
Dockerfile: lines 1-89, 90-313docker/stage2-hook.sh: lines 1-74, 97-139hermes_cli/service_manager.py: lines 1-85, 129-159hermes_cli/container_boot.py: lines 1-95docker/main-wrapper.sh: lines 1-10docker/entrypoint.sh: lines 1-28docker-compose.ymlwebsite/docs/user-guide/docker.md: lines 7-131tests/tools/test_dockerfile_pid1_reaping.py: lines 143-180docker/s6-rc.d/dashboard/rundocker/s6-rc.d/gateway/rundocker/cont-init.d/02-reconcile-profiles| File | Role |
|---|---|
Dockerfile | Builds the Docker image; installs system dependencies, s6-overlay, node, Python stuff. |
docker/stage2-hook.sh | Container bootstrap script run by s6 cont-init.d; UID/GID remap, volume ownership fixes. |
docker/entrypoint.sh | Deprecated shim; forwards to stage2-hook.sh with warning. |
docker/main-wrapper.sh | Runs as s6-supervised entrypoint wrapper to exec Hermes CLI or gateway commands. |
docker/s6-rc.d/dashboard/run | s6 service run script for the dashboard. |
docker/s6-rc.d/gateway/run | s6 service run script for per-profile gateway. |
hermes_cli/service_manager.py | Abstracts service control to Linux/macOS/Windows and s6 backends; manages gateway services. |
hermes_cli/container_boot.py | Handles syncing persistent profiles with ephemeral s6 service directories on container boot. |
Refresh this wiki
This wiki was recently refreshed. Please wait 4 days to refresh again.