Docker
Hermes Agent — Docker
Section titled “Hermes Agent — Docker”There are two distinct ways Docker intersects with Hermes Agent:
- Running Hermes IN Docker — the agent itself runs inside a container (this page’s primary focus)
- Docker as a terminal backend — the agent runs on your host but executes every command inside a single, persistent Docker sandbox container that survives across tool calls,
/new, and subagents for the life of the Hermes process (see Configuration → Docker Backend)
This page covers option 1. The container stores all user data (config, API keys, sessions, skills, memories) in a single directory mounted from the host at /opt/data. The image itself is stateless and can be upgraded by pulling a new version without losing any configuration.
Quick start
Section titled “Quick start”If this is your first time running Hermes Agent, create a data directory on the host and start the container interactively to run the setup wizard:
mkdir -p ~/.hermesdocker run -it --rm \ -v ~/.hermes:/opt/data \ nousresearch/hermes-agent setupThis drops you into the setup wizard, which will prompt you for your API keys and write them to ~/.hermes/.env. You only need to do this once. It is highly recommended to set up a chat system for the gateway to work with at this point.
Running in gateway mode
Section titled “Running in gateway mode”Once configured, run the container in the background as a persistent gateway (Telegram, Discord, Slack, WhatsApp, etc.):
docker run -d \ --name hermes \ --restart unless-stopped \ -v ~/.hermes:/opt/data \ -p 8642:8642 \ nousresearch/hermes-agent gateway runPort 8642 exposes the gateway’s OpenAI-compatible API server and health endpoint. It’s optional if you only use chat platforms (Telegram, Discord, etc.), but required if you want the dashboard or external tools to reach the gateway.
Note: the API server is gated on API_SERVER_ENABLED=true. To expose it beyond 127.0.0.1 inside the container, also set API_SERVER_HOST=0.0.0.0 and an API_SERVER_KEY (minimum 8 characters — generate one with openssl rand -hex 32). Example:
docker run -d \ --name hermes \ --restart unless-stopped \ -v ~/.hermes:/opt/data \ -p 8642:8642 \ -e API_SERVER_ENABLED=true \ -e API_SERVER_HOST=0.0.0.0 \ -e API_SERVER_KEY=your_api_key_here \ -e API_SERVER_CORS_ORIGINS='*' \ nousresearch/hermes-agent gateway runOpening any port on an internet facing machine is a security risk. You should not do it unless you understand the risks.
Running the dashboard
Section titled “Running the dashboard”The built-in web dashboard runs as an optional side-process inside the same container as the gateway. Set HERMES_DASHBOARD=1 and expose port 9119 alongside the gateway’s 8642:
docker run -d \ --name hermes \ --restart unless-stopped \ -v ~/.hermes:/opt/data \ -p 8642:8642 \ -p 9119:9119 \ -e HERMES_DASHBOARD=1 \ nousresearch/hermes-agent gateway runThe entrypoint starts hermes dashboard in the background (running as the non-root hermes user) before exec-ing the main command. Dashboard output is prefixed with [dashboard] in docker logs so it’s easy to separate from gateway logs.
| Environment variable | Description | Default |
|---|---|---|
HERMES_DASHBOARD | Set to 1 (or true / yes) to launch the dashboard alongside the main command | (unset — dashboard not started) |
HERMES_DASHBOARD_HOST | Bind address for the dashboard HTTP server | 0.0.0.0 |
HERMES_DASHBOARD_PORT | Port for the dashboard HTTP server | 9119 |
HERMES_DASHBOARD_TUI | Set to 1 to expose the in-browser Chat tab (embedded hermes --tui via PTY/WebSocket) | (unset) |
The default HERMES_DASHBOARD_HOST=0.0.0.0 is required for the host to reach the dashboard through the published port; the entrypoint automatically passes --insecure to hermes dashboard in that case. Override to 127.0.0.1 if you want to restrict the dashboard to in-container access only (e.g. behind a reverse proxy in a sidecar).
Running interactively (CLI chat)
Section titled “Running interactively (CLI chat)”To open an interactive chat session against a running data directory:
docker run -it --rm \ -v ~/.hermes:/opt/data \ nousresearch/hermes-agentOr if you have already opened a terminal in your running container (via Docker Desktop for instance), just run:
/opt/hermes/.venv/bin/hermesPersistent volumes
Section titled “Persistent volumes”The /opt/data volume is the single source of truth for all Hermes state. It maps to your host’s ~/.hermes/ directory and contains:
| Path | Contents |
|---|---|
.env | API keys and secrets |
config.yaml | All Hermes configuration |
SOUL.md | Agent personality/identity |
sessions/ | Conversation history |
memories/ | Persistent memory store |
skills/ | Installed skills |
cron/ | Scheduled job definitions |
hooks/ | Event hooks |
logs/ | Runtime logs |
skins/ | Custom CLI skins |
Never run two Hermes gateway containers against the same data directory simultaneously — session files and memory stores are not designed for concurrent write access.
Multi-profile support
Section titled “Multi-profile support”Hermes supports multiple profiles — separate ~/.hermes/ directories that let you run independent agents (different SOUL, skills, memory, sessions, credentials) from a single installation. When running under Docker, using Hermes’ built-in multi-profile feature is not recommended.
Instead, the recommended pattern is one container per profile, with each container bind-mounting its own host directory as /opt/data:
# Work profiledocker run -d \ --name hermes-work \ --restart unless-stopped \ -v ~/.hermes-work:/opt/data \ -p 8642:8642 \ nousresearch/hermes-agent gateway run
# Personal profiledocker run -d \ --name hermes-personal \ --restart unless-stopped \ -v ~/.hermes-personal:/opt/data \ -p 8643:8642 \ nousresearch/hermes-agent gateway runWhy separate containers over profiles in Docker:
- Isolation — each container has its own filesystem, process table, and resource limits. A crash, dependency change, or runaway session in one profile can’t affect another.
- Independent lifecycle — upgrade, restart, pause, or roll back each agent separately (
docker restart hermes-workleaveshermes-personaluntouched). - Clean port and network separation — each gateway binds its own host port; there’s no risk of cross-talk between chat platforms or API servers.
- Simpler mental model — the container is the profile. Backups, migrations, and permissions all follow the bind-mounted directory, with no extra
--profileflags to remember. - Avoids concurrent-write risk — the warning above about never running two gateways against the same data directory still applies to profiles within a single container.
In Docker Compose, this just means declaring one service per profile with distinct container_name, volumes, and ports:
services: hermes-work: image: nousresearch/hermes-agent:latest container_name: hermes-work restart: unless-stopped command: gateway run ports: - "8642:8642" volumes: - ~/.hermes-work:/opt/data
hermes-personal: image: nousresearch/hermes-agent:latest container_name: hermes-personal restart: unless-stopped command: gateway run ports: - "8643:8642" volumes: - ~/.hermes-personal:/opt/dataEnvironment variable forwarding
Section titled “Environment variable forwarding”API keys are read from /opt/data/.env inside the container. You can also pass environment variables directly:
docker run -it --rm \ -v ~/.hermes:/opt/data \ -e ANTHROPIC_API_KEY="sk-ant-..." \ -e OPENAI_API_KEY="sk-..." \ nousresearch/hermes-agentDirect -e flags override values from .env. This is useful for CI/CD or secrets-manager integrations where you don’t want keys on disk.
Docker Compose example
Section titled “Docker Compose example”For persistent deployment with both the gateway and dashboard, a docker-compose.yaml is convenient:
services: hermes: image: nousresearch/hermes-agent:latest container_name: hermes restart: unless-stopped command: gateway run ports: - "8642:8642" # gateway API - "9119:9119" # dashboard (only reached when HERMES_DASHBOARD=1) volumes: - ~/.hermes:/opt/data environment: - HERMES_DASHBOARD=1 # Uncomment to forward specific env vars instead of using .env file: # - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} # - OPENAI_API_KEY=${OPENAI_API_KEY} # - TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN} deploy: resources: limits: memory: 4G cpus: "2.0"Start with docker compose up -d and view logs with docker compose logs -f. Dashboard output is prefixed with [dashboard] so it’s easy to filter from gateway logs.
Resource limits
Section titled “Resource limits”The Hermes container needs moderate resources. Recommended minimums:
| Resource | Minimum | Recommended |
|---|---|---|
| Memory | 1 GB | 2–4 GB |
| CPU | 1 core | 2 cores |
| Disk (data volume) | 500 MB | 2+ GB (grows with sessions/skills) |
Browser automation (Playwright/Chromium) is the most memory-hungry feature. If you don’t need browser tools, 1 GB is sufficient. With browser tools active, allocate at least 2 GB.
Set limits in Docker:
docker run -d \ --name hermes \ --restart unless-stopped \ --memory=4g --cpus=2 \ -v ~/.hermes:/opt/data \ nousresearch/hermes-agent gateway runWhat the Dockerfile does
Section titled “What the Dockerfile does”The official image is based on debian:13.4 and includes:
- Python 3 with all Hermes dependencies (
uv pip install -e ".[all]") - Node.js + npm (for browser automation and WhatsApp bridge)
- Playwright with Chromium (
npx playwright install --with-deps chromium --only-shell) - ripgrep, ffmpeg, git, and tini as system utilities
docker-cli— so agents running inside the container can drive the host’s Docker daemon (bind-mount/var/run/docker.sockto opt in) fordocker build,docker run, container inspection, etc.openssh-client— enables the SSH terminal backend from inside the container. The SSH backend shells out to the systemsshbinary; without this, it failed silently in containerized installs.- The WhatsApp bridge (
scripts/whatsapp-bridge/)
The entrypoint script (docker/entrypoint.sh) bootstraps the data volume on first run:
- Creates the directory structure (
sessions/,memories/,skills/, etc.) - Copies
.env.example→.envif no.envexists - Copies default
config.yamlif missing - Copies default
SOUL.mdif missing - Syncs bundled skills using a manifest-based approach (preserves user edits)
- Optionally launches
hermes dashboardas a background side-process whenHERMES_DASHBOARD=1(see Running the dashboard) - Then runs
hermeswith whatever arguments you pass
Do not override the image entrypoint unless you keep /opt/hermes/docker/entrypoint.sh in the command chain. The entrypoint drops root privileges to the hermes user before gateway state files are created. Starting hermes gateway run as root inside the official image is refused by default because it can leave root-owned files in /opt/data and break later dashboard or gateway starts. Set HERMES_ALLOW_ROOT_GATEWAY=1 only when you intentionally accept that risk.
Upgrading
Section titled “Upgrading”Pull the latest image and recreate the container. Your data directory is untouched.
docker pull nousresearch/hermes-agent:latestdocker rm -f hermesdocker run -d \ --name hermes \ --restart unless-stopped \ -v ~/.hermes:/opt/data \ nousresearch/hermes-agent gateway runOr with Docker Compose:
docker compose pulldocker compose up -dSkills and credential files
Section titled “Skills and credential files”When using Docker as the execution environment (not the methods above, but when the agent runs commands inside a Docker sandbox — see Configuration → Docker Backend), Hermes reuses a single long-lived container for all tool calls and automatically bind-mounts the skills directory (~/.hermes/skills/) and any credential files declared by skills into that container as read-only volumes. Skill scripts, templates, and references are available inside the sandbox without manual configuration, and because the container persists for the life of the Hermes process, any dependencies you install or files you write stay around for the next tool call.
The same syncing happens for SSH and Modal backends — skills and credential files are uploaded via rsync or the Modal mount API before each command.
Connecting to local inference servers (vLLM, Ollama, etc.)
Section titled “Connecting to local inference servers (vLLM, Ollama, etc.)”When running Hermes in Docker and your inference server (vLLM, Ollama, text-generation-inference, etc.) is also running on the host or in another container, networking requires extra attention.
Docker Compose (recommended)
Section titled “Docker Compose (recommended)”Put both services on the same Docker network. This is the most reliable approach:
services: vllm: image: vllm/vllm-openai:latest container_name: vllm command: > --model Qwen/Qwen2.5-7B-Instruct --served-model-name my-model --host 0.0.0.0 --port 8000 ports: - "8000:8000" networks: - hermes-net deploy: resources: reservations: devices: - capabilities: [gpu]
hermes: image: nousresearch/hermes-agent:latest container_name: hermes restart: unless-stopped command: gateway run ports: - "8642:8642" volumes: - ~/.hermes:/opt/data networks: - hermes-net
networks: hermes-net: driver: bridgeThen in your ~/.hermes/config.yaml, use the container name as the hostname:
model: provider: custom model: my-model base_url: http://vllm:8000/v1 api_key: "none":::tip Key points
- Use the container name (
vllm) as the hostname — notlocalhostor127.0.0.1, which refer to the Hermes container itself. - The
modelvalue must match the--served-model-nameyou passed to vLLM. - Set
api_keyto any non-empty string (vLLM requires the header but doesn’t validate it by default). - Do not include a trailing slash in
base_url. :::
Standalone Docker run (no Compose)
Section titled “Standalone Docker run (no Compose)”If your inference server runs directly on the host (not in Docker), use host.docker.internal on macOS/Windows, or --network host on Linux:
macOS / Windows:
docker run -d \ --name hermes \ -v ~/.hermes:/opt/data \ -p 8642:8642 \ nousresearch/hermes-agent gateway runmodel: provider: custom model: my-model base_url: http://host.docker.internal:8000/v1 api_key: "none"Linux (host networking):
docker run -d \ --name hermes \ --network host \ -v ~/.hermes:/opt/data \ nousresearch/hermes-agent gateway runmodel: provider: custom model: my-model base_url: http://127.0.0.1:8000/v1 api_key: "none":::warning With --network host, the -p flag is ignored — all container ports are directly exposed on the host.
:::
Verifying connectivity
Section titled “Verifying connectivity”From inside the Hermes container, confirm the inference server is reachable:
docker exec hermes curl -s http://vllm:8000/v1/modelsYou should see a JSON response listing your served model. If this fails, check:
- Both containers are on the same Docker network (
docker network inspect hermes-net) - The inference server is listening on
0.0.0.0, not127.0.0.1 - The port number matches
Ollama
Section titled “Ollama”Ollama works the same way. If Ollama runs on the host, use host.docker.internal:11434 (macOS/Windows) or 127.0.0.1:11434 (Linux with --network host). If Ollama runs in its own container on the same Docker network:
model: provider: custom model: llama3 base_url: http://ollama:11434/v1 api_key: "none"Troubleshooting
Section titled “Troubleshooting”Container exits immediately
Section titled “Container exits immediately”Check logs: docker logs hermes. Common causes:
- Missing or invalid
.envfile — run interactively first to complete setup - Port conflicts if running with exposed ports
”Permission denied” errors
Section titled “”Permission denied” errors”The container’s entrypoint drops privileges to the non-root hermes user (UID 10000) via gosu. If your host ~/.hermes/ is owned by a different UID, set HERMES_UID/HERMES_GID to match your host user, or ensure the data directory is writable:
chmod -R 755 ~/.hermesBrowser tools not working
Section titled “Browser tools not working”Playwright needs shared memory. Add --shm-size=1g to your Docker run command:
docker run -d \ --name hermes \ --shm-size=1g \ -v ~/.hermes:/opt/data \ nousresearch/hermes-agent gateway runGateway not reconnecting after network issues
Section titled “Gateway not reconnecting after network issues”The --restart unless-stopped flag handles most transient failures. If the gateway is stuck, restart the container:
docker restart hermesChecking container health
Section titled “Checking container health”docker logs --tail 50 hermes # Recent logsdocker run -it --rm nousresearch/hermes-agent:latest version # Verify versiondocker stats hermes # Resource usage