Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions Caddyfile.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
admin off
auto_https off
}

:{$PORT} {
handle /health {
respond "ok" 200
}

handle {
basic_auth {
{$ADMIN_USERNAME} {$ADMIN_PASSWORD_HASH}
}
reverse_proxy 127.0.0.1:9119 {
header_up Host {upstream_hostport}
header_up X-Forwarded-Host {host}
}
}
}
52 changes: 6 additions & 46 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,37 +1,17 @@
FROM caddy:2-alpine AS caddy-bin

FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim

# Which hermes-agent revision to install. Accepts any git ref the upstream
# repo publishes — a release tag (recommended for reproducibility) or a
# branch name (`main`) for bleeding edge.
#
# To bump: check https://github.com/NousResearch/hermes-agent/releases for the
# newest tag (format `vYYYY.M.D`, e.g. `v2026.4.23`) and update the default
# below. Use `main` only if you accept that every rebuild can pull arbitrary
# new upstream commits.
ARG HERMES_REF=v2026.4.30

# tini = tiny init that we run as PID 1. Without it, hermes's grandchild
# processes (MCP stdio servers, git, bun, browser daemons spawned by tools)
# reparent to PID 1 when their parents exit and pile up as zombies. After
# weeks of uptime that exhausts the kernel's PID table → "fork: cannot
# allocate memory" and the container dies. tini reaps zombies in the
# background and forwards SIGTERM/SIGINT to our entrypoint so Railway's
# stop signal still triggers our graceful shutdown. Standard container init
# (same as Docker's `--init` flag and Kubernetes' pause container).
#
# Node.js is required only at build time to compile the Hermes React dashboard.
# We strip the source + apt lists afterwards to keep the image lean.
RUN apt-get update && \
apt-get install -y --no-install-recommends curl ca-certificates git tini && \
curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \
apt-get install -y --no-install-recommends nodejs && \
rm -rf /var/lib/apt/lists/*

# Install hermes-agent (provides the `hermes` CLI) and pre-build its React
# dashboard so `hermes dashboard` has nothing to build at runtime.
# Deleting web/ afterwards makes hermes's internal _build_web_ui skip the
# rebuild step (it early-returns when package.json is absent), so container
# startup is fast and no runtime npm dependency is needed.
COPY --from=caddy-bin /usr/bin/caddy /usr/local/bin/caddy

RUN git clone --depth 1 --branch ${HERMES_REF} https://github.com/NousResearch/hermes-agent.git /opt/hermes-agent && \
cd /opt/hermes-agent && \
uv pip install --system --no-cache -e ".[all]" && \
Expand All @@ -43,34 +23,14 @@ RUN git clone --depth 1 --branch ${HERMES_REF} https://github.com/NousResearch/h
npm run build && \
rm -rf /opt/hermes-agent/web /opt/hermes-agent/.git /root/.npm

# Why pre-build ui-tui (and why we don't delete it after):
# - The dashboard's embedded Chat tab spawns `node ui-tui/dist/entry.js`
# on every WebSocket connect to /api/pty.
# - hermes's _make_tui_argv runs `npm install` + `npm run build` via
# *synchronous* subprocess.run if dist/entry.js is missing or stale —
# that would block the dashboard's asyncio event loop for 30-60s on
# the first chat-open, freezing every other request.
# - Pre-building at image time costs ~200-300 MB of node_modules but
# makes first-chat-open instant and surfaces any build failure here
# instead of at user request time.
# - We keep ui-tui/ entirely (node_modules + dist + src) so hermes's
# freshness checks don't trigger a re-install at runtime.

COPY requirements.txt /app/requirements.txt
RUN uv pip install --system --no-cache -r /app/requirements.txt

RUN mkdir -p /data/.hermes
RUN mkdir -p /data/.hermes /app

COPY server.py /app/server.py
COPY templates/ /app/templates/
COPY Caddyfile.tmpl /app/Caddyfile.tmpl
COPY start.sh /app/start.sh
RUN chmod +x /app/start.sh

ENV HOME=/data
ENV HERMES_HOME=/data/.hermes

# tini wraps start.sh so it runs as PID 1's child instead of as PID 1 itself.
# `-g` propagates signals to the whole process group so `docker stop` /
# Railway's SIGTERM cleanly terminates the entire tree, not just start.sh.
ENTRYPOINT ["/usr/bin/tini", "-g", "--"]
CMD ["/app/start.sh"]
10 changes: 0 additions & 10 deletions requirements.txt

This file was deleted.

Loading