# syntax=docker/dockerfile:1 # Hermes Agent + Hermes Workspace — combined image # Builds on top of official image + our forked source + workspace UI. # Supports Swarm Mode (tmux workers) in a single container. # Requires Docker BuildKit. Pass SSH agent for git clone: # docker compose build hermes-workspace # ---------- Stage 1: Build Hermes Workspace (web UI) ---------- FROM node:22-slim AS workspace-build WORKDIR /app # Install pnpm and git RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates git curl \ && rm -rf /var/lib/apt/lists/* \ && corepack enable # Clone workspace source (pinned to a known-good commit on main) RUN git clone --depth 1 --branch main \ https://github.com/outsourc-e/hermes-workspace.git /app/workspace-src \ && rm -rf /app/workspace-src/.git WORKDIR /app/workspace-src # Install deps and build RUN pnpm install --frozen-lockfile && pnpm build # ---------- Stage 2: Hermes Agent + Workspace runtime ---------- FROM nousresearch/hermes-agent:latest # ---------- Install tmux for Swarm workers + curl for health checks ---------- # Note: Node.js is already shipped with the base hermets-agent image; apt's nodejs # would be older and could conflict. Only add what's missing. USER root RUN apt-get update && apt-get install -y --no-install-recommends \ tmux curl \ && rm -rf /var/lib/apt/lists/* # ---------- Overlay our forked Hermes source ---------- RUN --mount=type=ssh \ mkdir -p /root/.ssh && \ ssh-keyscan -p 2222 code.lazyworkhorse.net >> /root/.ssh/known_hosts 2>/dev/null && \ cd /tmp && \ GIT_SSH_COMMAND='ssh -p 2222 -o StrictHostKeyChecking=no' \ git clone --depth 1 --branch main \ git@code.lazyworkhorse.net:gortium/hermes-agent.git fork && \ rsync -a --delete fork/ /opt/hermes/ \ --exclude node_modules \ --exclude .venv \ --exclude .git && \ rm -rf /tmp/fork /root/.ssh/ # ---------- Rebuild web UI ---------- RUN cd /opt/hermes && npm run build # ---------- Reinstall Python package ---------- RUN . /opt/hermes/.venv/bin/activate && \ uv pip install --no-cache-dir --no-deps -e /opt/hermes # ---------- Extra system deps ---------- RUN apt-get update && apt-get install -y --no-install-recommends \ libportaudio2 ca-certificates poppler-utils imagemagick \ texlive-latex-base texlive-latex-extra texlive-fonts-recommended \ texlive-xetex texlive-science \ qemu-user-static binfmt-support emacs-nox \ && rm -rf /var/lib/apt/lists/* # ---------- UV ---------- COPY --chmod=0755 --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/ # ---------- Piper TTS ---------- RUN . /opt/hermes/.venv/bin/activate && \ uv pip install --no-cache-dir piper-tts sounddevice numpy && \ mkdir -p /opt/hermes/.venv/share/piper/voices RUN /opt/hermes/.venv/bin/python3 /dev/stdin << 'PYEOF' import urllib.request base = '/opt/hermes/.venv/share/piper/voices' url = 'https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/ryan/high/en_US-ryan-high.onnx' urllib.request.urlretrieve(url, base + '/en_US-ryan-high.onnx') urllib.request.urlretrieve(url + '.json', base + '/en_US-ryan-high.onnx.json') PYEOF # ---------- Install Himalaya email CLI ---------- RUN /opt/hermes/.venv/bin/python3 /dev/stdin << 'PYEOF' import urllib.request, tarfile, os, shutil url = 'https://github.com/pimalaya/himalaya/releases/download/v1.2.0/himalaya.x86_64-linux.tgz' tgz = '/tmp/himalaya.tgz' urllib.request.urlretrieve(url, tgz) with tarfile.open(tgz) as t: t.extractall('/tmp') shutil.move('/tmp/himalaya', '/usr/local/bin/himalaya') os.chmod('/usr/local/bin/himalaya', 0o755) os.remove(tgz) print('himalaya v1.2.0 installed') PYEOF # ---------- Install himalaya-ro wrapper ---------- COPY --chmod=0755 himalaya-ro.sh /usr/local/bin/himalaya-ro # ---------- Copy Hermes Workspace build artifacts ---------- COPY --from=workspace-build --chown=hermes:hermes \ /app/workspace-src/dist /workspace/dist COPY --from=workspace-build --chown=hermes:hermes \ /app/workspace-src/node_modules /workspace/node_modules COPY --from=workspace-build --chown=hermes:hermes \ /app/workspace-src/package.json /workspace/ COPY --from=workspace-build --chown=hermes:hermes \ /app/workspace-src/server-entry.js /workspace/ COPY --from=workspace-build --chown=hermes:hermes \ /app/workspace-src/skills /workspace/skills COPY --chmod=0755 entrypoint-combined.sh /usr/local/bin/entrypoint-combined.sh # ---------- Runtime ---------- USER hermes ENV HERMES_HOME=/opt/data ENV PATH="/opt/data/.local/bin:${PATH}" ENV CHROME_EXECUTABLE=/opt/hermes/.playwright/chromium/chrome-linux/chrome ENV HOST=0.0.0.0 ENV NODE_ENV=production RUN chown -R hermes:hermes /opt/hermes/tools /opt/hermes/toolsets.py VOLUME [ "/opt/data" ] EXPOSE 8642 9119 3000 ENTRYPOINT ["/usr/bin/tini", "-g", "--", "/usr/local/bin/entrypoint-combined.sh"]