Self-hosted AI Docker Compose stack

Hermes Agent and Ollama in one docker-compose file, from Hermes' own Docker and Ollama guides: a chat gateway on a local model, with no port open to the internet.

Prerequisites

  • A Linux server or desktop with Docker and the Compose plugin. Docker Desktop on macOS or Windows works, with the volume caveat under troubleshooting.
  • Memory for the model: Hermes' Ollama guide asks for 24 GB of RAM or more for gemma4:31b, the model it recommends because the agent works through tool calls.
  • A bot token for Telegram, Discord or Slack: the gateway talks to you through a chat platform.
  • Optional: a cloud API key (Anthropic, OpenRouter...) if you would rather not run the model yourself.

Steps

  1. Create the folders

    Hermes keeps all its state in one directory, mounted into the container at /opt/data: config.yaml, .env with the API keys, the sessions database state.db, memories and logs. The official image itself is stateless.

    mkdir -p ~/agent-stack ~/.hermes
    cd ~/agent-stack
  2. Write the compose file

    Two services on Compose's default network: Ollama serves the model and Hermes Agent runs as the chat gateway. Hermes' Docker guide says the gateway's port 8642 is only needed for its API and dashboard, so no port is published, and Hermes reaches Ollama by its service name. OLLAMA_CONTEXT_LENGTH raises Ollama's default context (4,096 tokens) to the 64,000 Hermes asks for, and OLLAMA_KEEP_ALIVE keeps the model loaded for a gateway that answers at any hour. The tags are pinned: v0.22.2 is the Hermes Agent release of 15 October 2026 and 0.40.6 the Ollama image on Docker Hub on 18 October 2026. Until then this page used a Hermes tag that does not exist (2026.4.4), HERMES_* settings Hermes does not document, and bundled Qdrant, which Hermes does not use without a plugin, and Caddy, which this stack does not need since nothing faces the internet.

    cat > docker-compose.yml <<'EOF'
    services:
      ollama:
        image: ollama/ollama:0.40.6
        container_name: ollama
        restart: unless-stopped
        environment:
          OLLAMA_CONTEXT_LENGTH: "64000"
          OLLAMA_KEEP_ALIVE: "24h"
        volumes:
          - ollama:/root/.ollama
    
      hermes:
        image: nousresearch/hermes-agent:v0.22.2
        container_name: hermes
        restart: unless-stopped
        command: gateway run
        volumes:
          - ~/.hermes:/opt/data
        depends_on:
          - ollama
    
    volumes:
      ollama:
    EOF
  3. Pull the model

    Hermes' Ollama guide recommends gemma4:31b, about 20 GB on disk, because it calls tools reliably; the smaller models it lists can chat but cannot call tools, so the agent could not act.

    docker compose up -d ollama
    docker compose exec ollama ollama pull gemma4:31b
    docker compose exec ollama ollama list
  4. Run Hermes' setup once

    The wizard writes ~/.hermes/config.yaml and ~/.hermes/.env. Choose Custom Endpoint, with http://ollama:11434/v1 as the base URL (the service name, not localhost), no API key and gemma4:31b as the model, then add your chat platform: the gateway needs one. The same model settings can go straight into config.yaml instead.

    docker compose run --rm hermes setup
    
    # or, in ~/.hermes/config.yaml:
    model:
      default: "gemma4:31b"
      provider: "custom"
      base_url: "http://ollama:11434/v1"
  5. Start the gateway

    In the official image the gateway runs under the s6 supervisor, which restarts it if it crashes. Besides docker logs, its rotated log stays on the volume in ~/.hermes/logs/gateways/default/current. Hermes' security docs ask for tight permissions on the .env that holds your keys.

    docker compose up -d
    docker compose logs -f hermes
    chmod 600 ~/.hermes/.env
  6. Optional: the web dashboard, over an SSH tunnel

    The dashboard runs in the same container when HERMES_DASHBOARD=1. Inside the container it listens on all interfaces, so Hermes refuses to start it without an authentication provider; the username and password provider is meant for a trusted network or a VPN, not the open internet. Publish its ports (8642 for the gateway, 9119 for the dashboard, as in Hermes' example) on the host's loopback only, and reach them through SSH or Tailscale. The password and the session secret come from a .env file next to the compose file.

    # add to the hermes service in docker-compose.yml:
        ports:
          - "127.0.0.1:8642:8642"
          - "127.0.0.1:9119:9119"
        environment:
          HERMES_DASHBOARD: "1"
          HERMES_DASHBOARD_BASIC_AUTH_USERNAME: "admin"
          HERMES_DASHBOARD_BASIC_AUTH_PASSWORD: ${HERMES_DASHBOARD_PASSWORD}
          HERMES_DASHBOARD_BASIC_AUTH_SECRET: ${HERMES_DASHBOARD_SECRET}
    
    # then, in ~/agent-stack:
    printf 'HERMES_DASHBOARD_PASSWORD=%s\nHERMES_DASHBOARD_SECRET=%s\n' \
      "$(openssl rand -hex 16)" "$(openssl rand -hex 32)" > .env
    chmod 600 .env
    docker compose up -d
    
    # from your laptop:
    ssh -L 9119:127.0.0.1:9119 you@your-server   # then open http://localhost:9119

Troubleshooting

Hermes can't reach Ollama
Inside the stack, services answer to their names: the base URL is http://ollama:11434/v1, not http://localhost:11434/v1. Check that docker compose exec ollama ollama list shows the model.
The agent chats but never runs a tool, or loses the thread
The model must support tool calls (Hermes' guide names gemma4:31b) and Ollama must give it at least 64,000 tokens of context: without OLLAMA_CONTEXT_LENGTH, Ollama's default is 4,096.
Answers take minutes on a CPU-only box
Hermes' Ollama guide expects about 2 to 5 tokens per second for a 31B model on a CPU. Give requests more time with HERMES_API_TIMEOUT=1800 in ~/.hermes/.env, or use a smaller model with tool calls, a GPU, or a cloud provider.
Warnings about state.db on Docker Desktop (macOS or Windows)
Bind mounts that cross Docker Desktop's VM (virtiofs, 9p) can corrupt a SQLite database in WAL mode. Hermes creates a fresh state.db in rollback mode there and warns about existing ones; the docs' other fix is a named volume for /opt/data instead of ~/.hermes.
Session or memory files look corrupted
Never run two Hermes containers on the same data directory: Hermes' Docker guide says its session files and memory stores do not support concurrent writers.

Where to go from here

This stack already follows Hermes' security policy for untrusted input: the whole agent runs inside its official image, so its shell commands run in the container, which only sees ~/.hermes. Updating means pulling a newer tag and recreating the container, since ~/.hermes keeps everything; pin the next release rather than latest and read its notes first. Prefer a cloud model? Run hermes setup again, pick the provider and remove the ollama service.

Sources

The steps and figures on this page come from these pages, read on 2026-10-18. When a command here and the project's docs disagree, the docs win.