diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..a690443f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,13 @@ +# Git +.git +.gitignore +.gitmodules + +# Dependencies +node_modules + +# CI/CD +.github + +# Environment files +.env \ No newline at end of file diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 00000000..11b98c3a --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,61 @@ +name: Docker Build and Publish + +on: + push: + branches: [main] + pull_request: + branches: [main] + +concurrency: + group: docker-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-and-push: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build image + uses: docker/build-push-action@v6 + with: + context: . + file: Dockerfile + load: true + tags: nousresearch/hermes-agent:test + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Test image starts + run: | + docker run --rm \ + -v /tmp/hermes-test:/opt/data \ + --entrypoint /opt/hermes/docker/entrypoint.sh \ + nousresearch/hermes-agent:test --help + + - name: Log in to Docker Hub + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Push image + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: docker/build-push-action@v6 + with: + context: . + file: Dockerfile + push: true + tags: | + nousresearch/hermes-agent:latest + nousresearch/hermes-agent:${{ github.sha }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..61b725d3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +FROM debian:13.4 + +RUN apt-get update +RUN apt-get install -y nodejs npm python3 python3-pip ripgrep ffmpeg gcc python3-dev libffi-dev + +COPY . /opt/hermes +WORKDIR /opt/hermes + +RUN pip install -e ".[all]" --break-system-packages +RUN npm install +RUN npx playwright install --with-deps chromium +WORKDIR /opt/hermes/scripts/whatsapp-bridge +RUN npm install + +WORKDIR /opt/hermes +RUN chmod +x /opt/hermes/docker/entrypoint.sh + +ENV HERMES_HOME=/opt/data +VOLUME [ "/opt/data" ] +ENTRYPOINT [ "/opt/hermes/docker/entrypoint.sh" ] \ No newline at end of file diff --git a/docker/SOUL.md b/docker/SOUL.md new file mode 100644 index 00000000..9103a612 --- /dev/null +++ b/docker/SOUL.md @@ -0,0 +1,15 @@ +# Hermes Agent Persona + + \ No newline at end of file diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100644 index 00000000..4c6366cb --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# Docker entrypoint: bootstrap config files into the mounted volume, then run hermes. +set -e + +HERMES_HOME="/opt/data" +INSTALL_DIR="/opt/hermes" + +# Create essential directory structure. Cache and platform directories +# (cache/images, cache/audio, platforms/whatsapp, etc.) are created on +# demand by the application — don't pre-create them here so new installs +# get the consolidated layout from get_hermes_dir(). +mkdir -p "$HERMES_HOME"/{cron,sessions,logs,hooks,memories,skills} + +# .env +if [ ! -f "$HERMES_HOME/.env" ]; then + cp "$INSTALL_DIR/.env.example" "$HERMES_HOME/.env" +fi + +# config.yaml +if [ ! -f "$HERMES_HOME/config.yaml" ]; then + cp "$INSTALL_DIR/cli-config.yaml.example" "$HERMES_HOME/config.yaml" +fi + +# SOUL.md +if [ ! -f "$HERMES_HOME/SOUL.md" ]; then + cp "$INSTALL_DIR/docker/SOUL.md" "$HERMES_HOME/SOUL.md" +fi + +# Sync bundled skills (manifest-based so user edits are preserved) +if [ -d "$INSTALL_DIR/skills" ]; then + python3 "$INSTALL_DIR/tools/skills_sync.py" +fi + +exec hermes "$@" diff --git a/website/docs/user-guide/docker.md b/website/docs/user-guide/docker.md new file mode 100644 index 00000000..22991977 --- /dev/null +++ b/website/docs/user-guide/docker.md @@ -0,0 +1,56 @@ +# Hermes Agent — Docker + +Want to run Hermes Agent, but without installing packages on your host? This'll sort you out. + +This will let you run the agent in a container, with the most relevant modes outlined below. + +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 + +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: + +```sh +mkdir -p ~/.hermes +docker run -it --rm \ + -v ~/.hermes:/opt/data \ + nousresearch/hermes-agent +``` + +This 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 + +Once configured, run the container in the background as a persistent gateway (Telegram, Discord, Slack, WhatsApp, etc.): + +```sh +docker run -d \ + --name hermes \ + --restart unless-stopped \ + -v ~/.hermes:/opt/data \ + nousresearch/hermes-agent gateway run +``` + +## Running interactively (CLI chat) + +To open an interactive chat session against a running data directory: + +```sh +docker run -it --rm \ + -v ~/.hermes:/opt/data \ + nousresearch/hermes-agent +``` + +## Upgrading + +Pull the latest image and recreate the container. Your data directory is untouched. + +```sh +docker pull nousresearch/hermes-agent:latest +docker rm -f hermes +docker run -d \ + --name hermes \ + --restart unless-stopped \ + -v ~/.hermes:/opt/data \ + nousresearch/hermes-agent +``` diff --git a/website/sidebars.ts b/website/sidebars.ts index c7fef2ae..10f34cd0 100644 --- a/website/sidebars.ts +++ b/website/sidebars.ts @@ -37,6 +37,7 @@ const sidebars: SidebarsConfig = { 'user-guide/configuration', 'user-guide/sessions', 'user-guide/security', + 'user-guide/docker', { type: 'category', label: 'Messaging Gateway',