From 8f71c03e5948a754860b53babf39b6fea509e335 Mon Sep 17 00:00:00 2001 From: thedavidweng <95214375+thedavidweng@users.noreply.github.com> Date: Thu, 4 Jun 2026 05:28:06 +0000 Subject: [PATCH 1/2] fix(docker): optimize image size with .dockerignore, drop dev deps, split build layers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three changes to reduce the Docker image size and speed up rebuilds: 1. .dockerignore — exclude ~69 MB of files that are never needed inside the container: apps/ (desktop Tauri source), tests/, website/ (Docusaurus), docs/, infographic/, nix/, plans/, packaging/, and various dotfiles (.envrc, .hadolint.yaml, .mailmap, etc.). The existing .dockerignore already covered node_modules and .git; these additions prevent the remaining non-runtime content from inflating both the build context and the final image (COPY . .). 2. pyproject.toml — add a [docker] extra that mirrors [all] but omits [dev] (debugpy, pytest, pytest-asyncio, pytest-timeout, ty, ruff, setuptools). The published image doesn't need test/debug tooling. Estimated savings: ~30-50 MB of Python packages. 3. Dockerfile — use --extra docker instead of --extra all in the uv sync layer. Also split the COPY + npm run build so that the web/ and ui-tui/ frontend builds are cached independently from Python source changes (COPY . .). A Python-only commit no longer invalidates the (slower) frontend build layer. Note: the build-only apt packages (gcc, python3-dev, libffi-dev, libolm-dev) are still installed in the final image. Removing them requires a true multi-stage build (builder → runtime), which is a larger refactor tracked separately. --- .dockerignore | 42 ++++++++++++++++++++++++++++++++++++++++++ Dockerfile | 25 +++++++++++++++---------- pyproject.toml | 14 ++++++++++++++ 3 files changed, 71 insertions(+), 10 deletions(-) diff --git a/.dockerignore b/.dockerignore index 3c16d71b2263..de8b95994bff 100644 --- a/.dockerignore +++ b/.dockerignore @@ -35,3 +35,45 @@ data/ # Compose/profile runtime state (bind-mounted; avoid ownership/secret issues) hermes-config/ runtime/ + +# ---------- Not needed inside the Docker image ---------- + +# Desktop app source (Tauri/Electron); never installed in the container +apps/ + +# Test suite — not shipped in production images +tests/ + +# Documentation site (Docusaurus) and supplementary docs +website/ +docs/ + +# Assets only used by the GitHub README +assets/ +infographic/ + +# Plugin-level docs (hermes-achievements ships docs/ but the runtime doesn't read them) +plugins/hermes-achievements/docs/ + +# Nix / Homebrew / AUR packaging metadata — irrelevant to Docker +nix/ +flake.nix +flake.lock +packaging/ + +# Design and planning documents +plans/ +.plans/ + +# ACP registry manifest (icon + agent.json) — not consumed at runtime +acp_registry/ + +# Repo-level dotfiles that are git-only or dev-tooling config +.env.example +.envrc +.gitattributes +.hadolint.yaml +.mailmap + +# Top-level LICENSE (not matched by *.md); not needed inside the container +LICENSE diff --git a/Dockerfile b/Dockerfile index 92522c5c41a1..aee1be4ce460 100644 --- a/Dockerfile +++ b/Dockerfile @@ -144,11 +144,12 @@ RUN npm install --prefer-offline --no-audit && \ # frontend stats the readme path during dep resolution, so we `touch` an # empty placeholder — the real README is restored by `COPY . .` below. # -# `uv sync --frozen --no-install-project --extra all --extra messaging` -# installs the deps reachable through the composite `[all]` extra -# (handpicked set intended for the production image), plus gateway -# messaging adapters that should work in the published image without a -# first-boot lazy install. We do NOT use `--all-extras`: +# `uv sync --frozen --no-install-project --extra docker --extra messaging` +# installs the deps reachable through the composite `[docker]` extra +# (identical to `[all]` but without `[dev]` — the published image doesn't +# need debugpy, pytest, ruff, ty, or setuptools), plus gateway messaging +# adapters that should work in the published image without a first-boot +# lazy install. We do NOT use `--all-extras`: # that would pull in `[rl]` (atroposlib + tinker + torch + wandb from # git), `[yc-bench]` (another git dep), and `[termux-all]` (Android # redundancy), none of which belong in the published container. @@ -167,16 +168,20 @@ RUN npm install --prefer-offline --no-audit && \ # The editable link is created after the source copy below. COPY pyproject.toml uv.lock ./ RUN touch ./README.md -RUN uv sync --frozen --no-install-project --extra all --extra messaging --extra anthropic --extra bedrock --extra azure-identity --extra hindsight +RUN uv sync --frozen --no-install-project --extra docker --extra messaging --extra anthropic --extra bedrock --extra azure-identity --extra hindsight + +# ---------- Frontend build (cached independently from Python source) ---------- +# Copy only the frontend source trees first so that Python-only changes don't +# invalidate the (relatively slow) web + ui-tui build layer. +COPY web/ web/ +COPY ui-tui/ ui-tui/ +RUN cd web && npm run build && \ + cd ../ui-tui && npm run build # ---------- Source code ---------- # .dockerignore excludes node_modules, so the installs above survive. COPY --chown=hermes:hermes . . -# Build browser dashboard and terminal UI assets. -RUN cd web && npm run build && \ - cd ../ui-tui && npm run build - # ---------- Permissions ---------- # Make install dir world-readable so any HERMES_UID can read it at runtime. # The venv needs to be traversable too. diff --git a/pyproject.toml b/pyproject.toml index 75e5b6d16777..8b973812fc11 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -232,6 +232,20 @@ all = [ "hermes-agent[web]", "hermes-agent[youtube]", ] +docker = [ + # `[all]` minus `[dev]` — the published Docker image doesn't need debugpy, + # pytest, ruff, ty, or setuptools. See Dockerfile `uv sync --extra docker`. + "hermes-agent[cron]", + "hermes-agent[cli]", + "hermes-agent[pty]", + "hermes-agent[mcp]", + "hermes-agent[homeassistant]", + "hermes-agent[sms]", + "hermes-agent[acp]", + "hermes-agent[google]", + "hermes-agent[web]", + "hermes-agent[youtube]", +] [project.scripts] hermes = "hermes_cli.main:main" From dc5751fe557edc3dea1ed193a787203d7d13735a Mon Sep 17 00:00:00 2001 From: thedavidweng <95214375+thedavidweng@users.noreply.github.com> Date: Tue, 9 Jun 2026 20:44:41 +0000 Subject: [PATCH 2/2] fix(docker): remove redundant [docker] extra, revert to --extra all MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The [docker] extra was identical to [all] on main — the PR had added [dev] to [all] then created [docker] as [all] minus [dev], a no-op round-trip. Revert [all] to its original form and drop the [docker] extra. Keep the .dockerignore additions and frontend build layer reordering. --- Dockerfile | 13 ++++++------- pyproject.toml | 15 --------------- 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/Dockerfile b/Dockerfile index aee1be4ce460..2e1ecac15792 100644 --- a/Dockerfile +++ b/Dockerfile @@ -144,12 +144,11 @@ RUN npm install --prefer-offline --no-audit && \ # frontend stats the readme path during dep resolution, so we `touch` an # empty placeholder — the real README is restored by `COPY . .` below. # -# `uv sync --frozen --no-install-project --extra docker --extra messaging` -# installs the deps reachable through the composite `[docker]` extra -# (identical to `[all]` but without `[dev]` — the published image doesn't -# need debugpy, pytest, ruff, ty, or setuptools), plus gateway messaging -# adapters that should work in the published image without a first-boot -# lazy install. We do NOT use `--all-extras`: +# `uv sync --frozen --no-install-project --extra all --extra messaging` +# installs the deps reachable through the composite `[all]` extra +# (handpicked set intended for the production image — excludes `[dev]`), +# plus gateway messaging adapters that should work in the published image +# without a first-boot lazy install. We do NOT use `--all-extras`: # that would pull in `[rl]` (atroposlib + tinker + torch + wandb from # git), `[yc-bench]` (another git dep), and `[termux-all]` (Android # redundancy), none of which belong in the published container. @@ -168,7 +167,7 @@ RUN npm install --prefer-offline --no-audit && \ # The editable link is created after the source copy below. COPY pyproject.toml uv.lock ./ RUN touch ./README.md -RUN uv sync --frozen --no-install-project --extra docker --extra messaging --extra anthropic --extra bedrock --extra azure-identity --extra hindsight +RUN uv sync --frozen --no-install-project --extra all --extra messaging --extra anthropic --extra bedrock --extra azure-identity --extra hindsight # ---------- Frontend build (cached independently from Python source) ---------- # Copy only the frontend source trees first so that Python-only changes don't diff --git a/pyproject.toml b/pyproject.toml index 8b973812fc11..56a9c537cfde 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -222,21 +222,6 @@ all = [ # where the user is expected to have a toolchain available. "hermes-agent[cron]", "hermes-agent[cli]", - "hermes-agent[dev]", - "hermes-agent[pty]", - "hermes-agent[mcp]", - "hermes-agent[homeassistant]", - "hermes-agent[sms]", - "hermes-agent[acp]", - "hermes-agent[google]", - "hermes-agent[web]", - "hermes-agent[youtube]", -] -docker = [ - # `[all]` minus `[dev]` — the published Docker image doesn't need debugpy, - # pytest, ruff, ty, or setuptools. See Dockerfile `uv sync --extra docker`. - "hermes-agent[cron]", - "hermes-agent[cli]", "hermes-agent[pty]", "hermes-agent[mcp]", "hermes-agent[homeassistant]",