← All Guides

Hermes Guide

Hermes Agent with docker-compose

A docker-compose file is the fastest self-hosted way to run Hermes Agent — one YAML, one docker compose up -d, gateway online. This guide gives you a working compose file, explains each block, covers the credentials and TLS setup people usually get wrong, and points at OpenClaw Launch for when you'd rather not run it yourself.

Prerequisites

  • A Linux host (Debian/Ubuntu works) with 2 GB of RAM minimum, 4 GB recommended
  • Docker Engine 24+ and the compose v2 plugin
  • A public hostname pointed at the host (for TLS). Not strictly required if you only want local use
  • An OpenRouter / Anthropic / OpenAI API key
  • A Telegram bot token if you're going to pair Telegram (most people do)

Directory Layout

Keep everything under one folder so you can move it or back it up in one tar:

/opt/hermes/ docker-compose.yml hermes.json          # agent config credentials/         # channel sessions live here data/                # memory, skills state logs/

The docker-compose.yml

A minimal working file:

services:
  hermes:
    image: ghcr.io/nousresearch/hermes-agent:latest
    container_name: hermes
    restart: unless-stopped
    ports:
      - "127.0.0.1:8642:8642"   # gateway + web UI (bind localhost; put Caddy in front)
    environment:
      OPENROUTER_API_KEY: "${OPENROUTER_API_KEY}"
      TELEGRAM_BOT_TOKEN: "${TELEGRAM_BOT_TOKEN}"
    volumes:
      - ./hermes.json:/app/hermes.json:ro
      - ./credentials:/root/.hermes/credentials
      - ./data:/root/.hermes/data
      - ./logs:/root/.hermes/logs

Things to call out:

  • 127.0.0.1:8642 — bind to localhost only. Never publish the gateway on 0.0.0.0 without TLS in front.
  • Credentials volume — the credentials/ directory MUST exist before first boot. Telegram pairing silently drops messages if it can't write sessions.
  • Config as read-only — mount hermes.json with :ro so the container can't accidentally rewrite it on you.
  • Pin the image — for production use a version tag (e.g. v2026.4.16) instead of :latest.

The hermes.json

A minimal agent config with Telegram, a single model, and the gateway exposed:

{
  "gateway": {
    "host": "0.0.0.0",
    "port": 8642,
    "auth": { "token": "CHANGE-ME-TO-A-LONG-RANDOM-STRING" },
    "controlUi": { "allowInsecureAuth": true }
  },
  "models": {
    "default": "openrouter/anthropic/claude-sonnet-4.6",
    "providers": {
      "openrouter": { "apiKey": "${OPENROUTER_API_KEY}" }
    }
  },
  "channels": {
    "telegram": {
      "enabled": true,
      "token": "${TELEGRAM_BOT_TOKEN}",
      "dmPolicy": "pairing"
    }
  },
  "plugins": {
    "entries": {
      "telegram": { "enabled": true }
    }
  }
}

If you later add WhatsApp, Discord, WeChat, or Feishu, flip them on here and add a matching plugins.entries.X.enabled: true — both are required for a channel to actually start.

First Boot

mkdir -p /opt/hermes/credentials /opt/hermes/data /opt/hermes/logs
cd /opt/hermes
cp docker-compose.yml hermes.json .
export OPENROUTER_API_KEY=...
export TELEGRAM_BOT_TOKEN=...
docker compose up -d
docker compose logs -f hermes

A healthy log ends with the gateway listening on 8642 and the Telegram plugin connected. If you see “credentials dir missing” or repeated channels login warnings, the credentials/ volume mount isn't set up — fix it and restart.

Adding TLS with Caddy

Put Caddy in the same compose file and let it auto-issue a cert:

  caddy:
    image: caddy:2
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config

volumes:
  caddy_data:
  caddy_config:

And a one-line Caddyfile:

hermes.example.com {
  reverse_proxy hermes:8642
}

Point DNS at the host, docker compose up -d, and the web UI is reachable at https://hermes.example.com/ui with auto-renewing TLS.

Common Pitfalls

  • Running as :latest in production — an upstream change can break your config between restarts. Always pin.
  • Skipping the credentials mount — Telegram pairing will appear to work, then silently drop every message.
  • Running without a reverse proxy — the gateway speaks plain HTTP. Tokens will leak if you skip TLS.
  • Editing hermes.json on a running container — some sections (gateway.auth, plugins) require a restart. Chat-safe hot-reload covers channels, models, agents, and that's mostly it.
  • Forgetting to rotate gateway.auth.token — the default example token is famous. Generate a long random string on first boot and never commit it.

Backup

One tar covers you:

tar czf /opt/backups/hermes-$(date +%F).tar.gz \
  -C /opt/hermes credentials data hermes.json

credentials/ is the sensitive part — it holds the session state for each chat channel. Keep those backups encrypted.

Skip It: OpenClaw Launch

The compose file above is fine for a weekend project. Run it in production and you inherit TLS renewal, image pinning, channel plugin installs, credentials backups, monitoring, and the inevitable “why did the container OOM at 3am” debugging.

Hermes hosting on OpenClaw Launch is the same Hermes Agent gateway — same config, same channels, same skills — on managed infrastructure with a warm pool, auto-TLS, backups, and a 10-second deploy. $6/mo on Lite after a $3 first month.

Next Steps

Not Excited About Running Docker?

Same Hermes Agent gateway, zero ops work. Deployed in 10 seconds.

Hermes Hosting