mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
fix(docker): run as non-root user, use virtualenv (salvage #5811)
- Add gosu for runtime privilege dropping from root to hermes user - Support HERMES_UID/HERMES_GID env vars for host mount permission matching - Switch to debian:13.4-slim base image - Use uv venv instead of pip install --break-system-packages - Pin uv and gosu multi-stage images with SHA256 digests - Set PLAYWRIGHT_BROWSERS_PATH to /opt/hermes/.playwright so build-time chromium install survives the /opt/data volume mount - Keep procps for container debugging Based on work by m0n5t3r in PR #5811. Stripped to hardening-only changes (non-root, virtualenv, slim base); matrix deps, fonts, xvfb, and entrypoint playwright download deferred to follow-up.
This commit is contained in:
parent
81ac62c0e9
commit
fee0e0d35e
2 changed files with 51 additions and 7 deletions
31
Dockerfile
31
Dockerfile
|
|
@ -1,27 +1,44 @@
|
||||||
FROM debian:13.4
|
FROM ghcr.io/astral-sh/uv:0.11.6-python3.13-trixie@sha256:b3c543b6c4f23a5f2df22866bd7857e5d304b67a564f4feab6ac22044dde719b AS uv_source
|
||||||
|
FROM tianon/gosu:1.19-trixie@sha256:3b176695959c71e123eb390d427efc665eeb561b1540e82679c15e992006b8b9 AS gosu_source
|
||||||
|
FROM debian:13.4-slim
|
||||||
|
|
||||||
# Disable Python stdout buffering to ensure logs are printed immediately
|
# Disable Python stdout buffering to ensure logs are printed immediately
|
||||||
ENV PYTHONUNBUFFERED=1
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
# Store Playwright browsers outside the volume mount so the build-time
|
||||||
|
# install survives the /opt/data volume overlay at runtime.
|
||||||
|
ENV PLAYWRIGHT_BROWSERS_PATH=/opt/hermes/.playwright
|
||||||
|
|
||||||
# Install system dependencies in one layer, clear APT cache
|
# Install system dependencies in one layer, clear APT cache
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
build-essential nodejs npm python3 python3-pip ripgrep ffmpeg gcc python3-dev libffi-dev procps && \
|
build-essential nodejs npm python3 ripgrep ffmpeg gcc python3-dev libffi-dev procps && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Non-root user for runtime; UID can be overridden via HERMES_UID at runtime
|
||||||
|
RUN useradd -u 10000 -m -d /opt/data hermes
|
||||||
|
|
||||||
|
COPY --chmod=0755 --from=gosu_source /gosu /usr/local/bin/
|
||||||
|
COPY --chmod=0755 --from=uv_source /usr/local/bin/uv /usr/local/bin/uvx /usr/local/bin/
|
||||||
|
|
||||||
COPY . /opt/hermes
|
COPY . /opt/hermes
|
||||||
WORKDIR /opt/hermes
|
WORKDIR /opt/hermes
|
||||||
|
|
||||||
# Install Python and Node dependencies in one layer, no cache
|
# Install Node dependencies and Playwright as root (--with-deps needs apt)
|
||||||
RUN pip install --no-cache-dir uv --break-system-packages && \
|
RUN npm install --prefer-offline --no-audit && \
|
||||||
uv pip install --system --break-system-packages --no-cache -e ".[all]" && \
|
|
||||||
npm install --prefer-offline --no-audit && \
|
|
||||||
npx playwright install --with-deps chromium --only-shell && \
|
npx playwright install --with-deps chromium --only-shell && \
|
||||||
cd /opt/hermes/scripts/whatsapp-bridge && \
|
cd /opt/hermes/scripts/whatsapp-bridge && \
|
||||||
npm install --prefer-offline --no-audit && \
|
npm install --prefer-offline --no-audit && \
|
||||||
npm cache clean --force
|
npm cache clean --force
|
||||||
|
|
||||||
WORKDIR /opt/hermes
|
# Hand ownership to hermes user, then install Python deps in a virtualenv
|
||||||
|
RUN chown -R hermes:hermes /opt/hermes
|
||||||
|
USER hermes
|
||||||
|
|
||||||
|
RUN uv venv && \
|
||||||
|
uv pip install --no-cache-dir -e ".[all]"
|
||||||
|
|
||||||
|
USER root
|
||||||
RUN chmod +x /opt/hermes/docker/entrypoint.sh
|
RUN chmod +x /opt/hermes/docker/entrypoint.sh
|
||||||
|
|
||||||
ENV HERMES_HOME=/opt/data
|
ENV HERMES_HOME=/opt/data
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,33 @@ set -e
|
||||||
HERMES_HOME="/opt/data"
|
HERMES_HOME="/opt/data"
|
||||||
INSTALL_DIR="/opt/hermes"
|
INSTALL_DIR="/opt/hermes"
|
||||||
|
|
||||||
|
# --- Privilege dropping via gosu ---
|
||||||
|
# When started as root (the default), optionally remap the hermes user/group
|
||||||
|
# to match host-side ownership, fix volume permissions, then re-exec as hermes.
|
||||||
|
if [ "$(id -u)" = "0" ]; then
|
||||||
|
if [ -n "$HERMES_UID" ] && [ "$HERMES_UID" != "$(id -u hermes)" ]; then
|
||||||
|
echo "Changing hermes UID to $HERMES_UID"
|
||||||
|
usermod -u "$HERMES_UID" hermes
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$HERMES_GID" ] && [ "$HERMES_GID" != "$(id -g hermes)" ]; then
|
||||||
|
echo "Changing hermes GID to $HERMES_GID"
|
||||||
|
groupmod -g "$HERMES_GID" hermes
|
||||||
|
fi
|
||||||
|
|
||||||
|
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"
|
||||||
|
chown -R hermes:hermes "$HERMES_HOME"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Dropping root privileges"
|
||||||
|
exec gosu hermes "$0" "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Running as hermes from here ---
|
||||||
|
source "${INSTALL_DIR}/.venv/bin/activate"
|
||||||
|
|
||||||
# Create essential directory structure. Cache and platform directories
|
# Create essential directory structure. Cache and platform directories
|
||||||
# (cache/images, cache/audio, platforms/whatsapp, etc.) are created on
|
# (cache/images, cache/audio, platforms/whatsapp, etc.) are created on
|
||||||
# demand by the application — don't pre-create them here so new installs
|
# demand by the application — don't pre-create them here so new installs
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue