From 14c9f7272c0d35832e795e257c80a7f1e9a82bc6 Mon Sep 17 00:00:00 2001 From: Jiecheng Wu Date: Tue, 21 Apr 2026 19:12:15 +0800 Subject: [PATCH] fix(docker): fix HERMES_UID permission handling and add docker-compose.yml - Remove 'USER hermes' from Dockerfile so entrypoint runs as root and can usermod/groupmod before gosu drop. Add chmod -R a+rX /opt/hermes so any remapped UID can read the install directory. - Fix entrypoint chown logic: always chown -R when HERMES_UID is remapped from default 10000, not just when top-level dir ownership mismatches. - Add docker-compose.yml with gateway + dashboard services. - Add .hermes to .gitignore. --- Dockerfile | 10 ++++++++-- docker-compose.yml | 30 ++++++++++++++++++++++++++++++ docker/entrypoint.sh | 13 +++++++++++-- 3 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 docker-compose.yml diff --git a/Dockerfile b/Dockerfile index 8904c4c74..fd5f18b4b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -41,9 +41,15 @@ COPY --chown=hermes:hermes . . # Build web dashboard (Vite outputs to hermes_cli/web_dist/) RUN cd web && 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. +USER root +RUN chmod -R a+rX /opt/hermes +# Start as root so the entrypoint can usermod/groupmod + gosu. +# If HERMES_UID is unset, the entrypoint drops to the default hermes user (10000). + # ---------- Python virtualenv ---------- -RUN chown hermes:hermes /opt/hermes -USER hermes RUN uv venv && \ uv pip install --no-cache-dir -e ".[all]" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..4acb15306 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,30 @@ +services: + gateway: + build: . + image: hermes-agent + container_name: hermes + restart: unless-stopped + network_mode: host + volumes: + - ~/.hermes:/opt/data + environment: + - HERMES_UID=${HERMES_UID:-1001} + - HERMES_GID=${HERMES_GID:-1001} + # Uncomment to expose API server beyond localhost (requires API_SERVER_KEY): + # - API_SERVER_HOST=0.0.0.0 + # - API_SERVER_KEY=${API_SERVER_KEY} + command: ["gateway", "run"] + + dashboard: + image: hermes-agent + container_name: hermes-dashboard + restart: unless-stopped + network_mode: host + depends_on: + - gateway + volumes: + - ~/.hermes:/opt/data + environment: + - HERMES_UID=${HERMES_UID:-1001} + - HERMES_GID=${HERMES_GID:-1001} + command: ["dashboard", "--host", "0.0.0.0", "--insecure"] diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 67d193f13..0be1d656c 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -22,9 +22,18 @@ if [ "$(id -u)" = "0" ]; then groupmod -o -g "$HERMES_GID" hermes 2>/dev/null || true fi + # Fix ownership of the data volume. When HERMES_UID remaps the hermes user, + # files created by previous runs (under the old UID) become inaccessible. + # Always chown -R when UID was remapped; otherwise only if top-level is wrong. actual_hermes_uid=$(id -u hermes) - if [ "$(stat -c %u "$HERMES_HOME" 2>/dev/null)" != "$actual_hermes_uid" ]; then - echo "$HERMES_HOME is not owned by $actual_hermes_uid, fixing" + needs_chown=false + if [ -n "$HERMES_UID" ] && [ "$HERMES_UID" != "10000" ]; then + needs_chown=true + elif [ "$(stat -c %u "$HERMES_HOME" 2>/dev/null)" != "$actual_hermes_uid" ]; then + needs_chown=true + fi + if [ "$needs_chown" = true ]; then + echo "Fixing ownership of $HERMES_HOME to hermes ($actual_hermes_uid)" # In rootless Podman the container's "root" is mapped to an unprivileged # host UID — chown will fail. That's fine: the volume is already owned # by the mapped user on the host side.