hermes / Dockerfile
F4bC0d3
fix(logs): silence noisy WebUI access log polls + bound on-disk log size
aadc662
# HuggingMes + Hermes WebUI — merged deployment for Hugging Face Spaces
# Base: NousResearch Hermes Agent (ships Hermes CLI, gateway, dashboard, Python venv)
ARG HERMES_AGENT_VERSION=latest
FROM nousresearch/hermes-agent:${HERMES_AGENT_VERSION}
ARG WEBUI_REF=master
USER root
# System deps (mirrors HuggingMes) + git/nodejs for WebUI checkout + router
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
curl \
jq \
git \
python3 \
nodejs \
npm \
chromium \
libnss3 \
libatk1.0-0 \
libatk-bridge2.0-0 \
libdrm2 \
libgbm1 \
libxcomposite1 \
libxdamage1 \
libxrandr2 \
libxkbcommon0 \
libx11-6 \
libxext6 \
libxfixes3 \
libasound2 \
fonts-dejavu-core \
fonts-liberation \
fonts-noto-color-emoji \
&& rm -rf /var/lib/apt/lists/* \
&& uv pip install --python /opt/hermes/.venv/bin/python --no-cache-dir \
huggingface_hub hf_transfer pyyaml
# Clone nesquena/hermes-webui (install deps into the agent venv so imports resolve)
RUN git clone --depth 1 --branch ${WEBUI_REF} \
https://github.com/nesquena/hermes-webui.git /opt/hermes-webui \
&& ( [ -f /opt/hermes-webui/requirements.txt ] \
&& /opt/hermes/.venv/bin/pip install --no-cache-dir -r /opt/hermes-webui/requirements.txt \
|| true ) \
&& chown -R hermes:hermes /opt/hermes-webui
# HuggingMes-style integration scripts (vendored from somratpro/HuggingMes)
COPY --chown=hermes:hermes start.sh /opt/huggingmes/start.sh
COPY --chown=hermes:hermes health-server.js /opt/huggingmes/health-server.js
COPY --chown=hermes:hermes hermes-sync.py /opt/huggingmes/hermes-sync.py
COPY --chown=hermes:hermes cloudflare-proxy-setup.py /opt/huggingmes/cloudflare-proxy-setup.py
COPY --chown=hermes:hermes cloudflare-keepalive-setup.py /opt/huggingmes/cloudflare-keepalive-setup.py
RUN chmod +x \
/opt/huggingmes/start.sh \
/opt/huggingmes/hermes-sync.py \
/opt/huggingmes/cloudflare-proxy-setup.py \
/opt/huggingmes/cloudflare-keepalive-setup.py
# Idempotent kanban migration patch (same workaround HuggingMes ships)
RUN python3 - <<'PY'
from pathlib import Path
import sys
p = Path("/opt/hermes/hermes_cli/kanban_db.py")
if not p.exists():
sys.exit(0)
src = p.read_text(encoding="utf-8")
sentinel = "# huggingmes-webui: idempotent-alter"
if sentinel in src:
sys.exit(0)
old = (
' conn.execute(\n'
' "ALTER TABLE tasks ADD COLUMN consecutive_failures "\n'
' "INTEGER NOT NULL DEFAULT 0"\n'
' )'
)
new = (
f' try: {sentinel}\n'
' conn.execute(\n'
' "ALTER TABLE tasks ADD COLUMN consecutive_failures "\n'
' "INTEGER NOT NULL DEFAULT 0"\n'
' )\n'
' except Exception:\n'
' pass'
)
if old in src:
p.write_text(src.replace(old, new), encoding="utf-8")
print("kanban patch: applied")
PY
# Quiet hermes-webui's per-request access log noise.
# By default it prints `[webui] {"ts":...,"method":...}` for EVERY request,
# which drowns the HF Logs tab once any browser tab is open polling
# /api/dashboard/status, /api/health/agent, /api/sessions, /sw.js, etc.
# Patch log_request() to drop 2xx responses for high-frequency poll paths.
# Errors and chat/streaming paths still log normally.
RUN python3 - <<'PY'
from pathlib import Path
import re
import sys
p = Path("/opt/hermes-webui/server.py")
if not p.exists():
sys.exit(0)
src = p.read_text(encoding="utf-8")
sentinel = "# huggingmes-webui: quiet-poll-paths"
if sentinel in src:
sys.exit(0)
old = (
" def log_request(self, code: str='-', size: str='-') -> None:\n"
" \"\"\"Structured JSON logs for each request.\"\"\"\n"
" import json as _json\n"
" duration_ms = round((time.time() - getattr(self, '_req_t0', time.time())) * 1000, 1)\n"
" record = _json.dumps({\n"
" 'ts': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),\n"
" 'method': self.command or '-',\n"
" 'path': self.path or '-',\n"
" 'status': int(code) if str(code).isdigit() else code,\n"
" 'ms': duration_ms,\n"
" })\n"
" print(f'[webui] {record}', flush=True)"
)
new = (
" _QUIET_POLL_PATHS = ( " + sentinel + "\n"
" '/api/health/agent', '/api/dashboard/status',\n"
" '/api/dashboard/config', '/api/sessions', '/api/profiles',\n"
" '/api/profile/active', '/api/onboarding/status',\n"
" '/api/insights', '/api/system/health',\n"
" '/api/settings', '/api/projects', '/api/reasoning',\n"
" '/api/models', '/api/chat/stream/status',\n"
" '/api/git-info', '/sw.js', '/health',\n"
" )\n"
" _QUIET_PREFIXES = ('/static/', '/session/static/', '/assets/')\n"
"\n"
" def log_request(self, code: str='-', size: str='-') -> None:\n"
" \"\"\"Structured JSON logs for each request, skipping noisy polls.\"\"\"\n"
" # Always log non-2xx so 401/404/5xx remain visible.\n"
" try:\n"
" status_int = int(code) if str(code).isdigit() else 0\n"
" except Exception:\n"
" status_int = 0\n"
" path = (self.path or '').split('?', 1)[0]\n"
" if 200 <= status_int < 400:\n"
" if path in self._QUIET_POLL_PATHS:\n"
" return\n"
" for pref in self._QUIET_PREFIXES:\n"
" if path.startswith(pref):\n"
" return\n"
" import json as _json\n"
" duration_ms = round((time.time() - getattr(self, '_req_t0', time.time())) * 1000, 1)\n"
" record = _json.dumps({\n"
" 'ts': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),\n"
" 'method': self.command or '-',\n"
" 'path': self.path or '-',\n"
" 'status': int(code) if str(code).isdigit() else code,\n"
" 'ms': duration_ms,\n"
" })\n"
" print(f'[webui] {record}', flush=True)"
)
if old in src:
p.write_text(src.replace(old, new), encoding="utf-8")
print("webui log-quiet patch: applied")
else:
print("webui log-quiet patch: pattern not found, skipping")
PY
# Keep hermes CLI on PATH for all shell types (login/interactive/non-interactive)
RUN echo 'export PATH="/opt/hermes/.venv/bin:/opt/data/.local/bin:$PATH"' \
> /etc/profile.d/hermes-venv.sh
ENV HERMES_HOME=/opt/data \
HUGGINGMES_APP_DIR=/opt/huggingmes \
HERMES_WEBUI_REPO=/opt/hermes-webui \
HERMES_AGENT_VERSION=${HERMES_AGENT_VERSION} \
PYTHONUNBUFFERED=1 \
HF_HUB_ENABLE_HF_TRANSFER=1 \
PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium
EXPOSE 7861
HEALTHCHECK --interval=30s --timeout=5s --start-period=120s \
CMD curl -fsS http://localhost:7861/health || exit 1
USER hermes
CMD ["/opt/huggingmes/start.sh"]