-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathDockerfile
More file actions
136 lines (116 loc) · 5.64 KB
/
Copy pathDockerfile
File metadata and controls
136 lines (116 loc) · 5.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# images/hermes-agent/Dockerfile
#
# Two-stage multi-arch build for ghcr.io/paperclipinc/hermes-agent.
# Stage 1 (`builder`) uses the official uv image to resolve and install the pinned
# Python environment into a self-contained venv at /opt/venv. Stage 2 (`runtime`)
# copies only the venv + minimal apt deps and runs as the non-root `hermes` user.
#
# Build args:
# HERMES_VERSION : the exact hermes-agent release to package (e.g. "1.4.3").
# Required. The CI workflow at .github/workflows/agent-image.yaml
# drives this from a matrix.
# PYTHON_VERSION : Python interpreter version. Default 3.11.
# UV_VERSION : uv tool version. Default 0.11.7 (pinned for reproducibility; keep in lockstep with the relock workflow).
# TINI_VERSION : tini release tag (lesson openclaw #471: tini as PID 1).
#
# Output: a runtime image that, with no further configuration, runs
# `hermes-agent --config ~/.hermes/config.yaml` as UID 1000.
ARG PYTHON_VERSION=3.11
ARG UV_VERSION=0.11.7
ARG TINI_VERSION=v0.19.0
ARG HERMES_VERSION
# ---------- Stage 1: builder ----------
FROM ghcr.io/astral-sh/uv:${UV_VERSION} AS uv
FROM python:${PYTHON_VERSION}-slim-bookworm AS builder
ARG HERMES_VERSION
# uv binary from the dedicated stage above.
COPY --from=uv /uv /uvx /usr/local/bin/
ENV UV_LINK_MODE=copy \
UV_COMPILE_BYTECODE=1 \
UV_PYTHON_DOWNLOADS=never \
VIRTUAL_ENV=/opt/venv \
UV_PROJECT_ENVIRONMENT=/opt/venv
WORKDIR /build
# git + ca-certificates are needed at lock-resolve time because the lockfile
# pins hermes-agent by git ref (upstream is not on PyPI).
RUN apt-get update \
&& apt-get install -y --no-install-recommends git ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# Copy lockfile + project metadata first for layer caching.
COPY pyproject.toml uv.lock ./
# Sync the frozen lockfile. `make agent-image-relock HERMES_VERSION=vX.Y.Z`
# rewrites the @ ref in pyproject.toml and regenerates uv.lock; at build time
# we simply trust the lock. HERMES_VERSION is informational for the labels stage.
RUN --mount=type=cache,target=/root/.cache/uv \
set -eux; \
if [ -z "${HERMES_VERSION}" ]; then \
echo "ERROR: HERMES_VERSION build-arg is required" >&2; exit 1; \
fi; \
uv venv /opt/venv; \
uv sync --frozen --no-dev
# ---------- Stage 2: runtime ----------
FROM python:${PYTHON_VERSION}-slim-bookworm AS runtime
ARG TINI_VERSION
ARG HERMES_VERSION
ARG TARGETARCH
# Runtime apt packages:
# - ffmpeg, ripgrep: hard dependencies of hermes-agent (audio + search)
# - git, openssh-client: hermes-agent skill installs are git-based
# - ca-certificates: TLS to platform gateways
# - tini: PID 1 reaper (lesson openclaw-operator #471)
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ffmpeg \
ripgrep \
git \
openssh-client \
ca-certificates \
tini \
&& rm -rf /var/lib/apt/lists/*
# Non-root user matching the StatefulSet's runAsUser=1000 (Plan 1).
RUN groupadd --system --gid 1000 hermes \
&& useradd --system --uid 1000 --gid 1000 --create-home --shell /usr/sbin/nologin hermes \
&& mkdir -p /home/hermes/.hermes \
&& chown -R hermes:hermes /home/hermes
# Copy the resolved venv from the builder stage.
COPY --from=builder --chown=hermes:hermes /opt/venv /opt/venv
# Ship the lockfile + project metadata at /opt/venv-template/ as well. The
# operator's `init-uv` init container (internal/resources/runtime_init.go) does
# cd /home/hermes/.hermes; cp /opt/venv-template/pyproject.toml /opt/venv-template/uv.lock .; uv sync --frozen
# to materialise the env into the per-instance PVC. Without these files the init
# container exits 1 ("cp: cannot stat '/opt/venv-template/pyproject.toml'") and
# no HermesInstance ever reaches Ready. See #68.
COPY --from=builder --chown=hermes:hermes /build/pyproject.toml /build/uv.lock /opt/venv-template/
# Ship the uv binary in the runtime image too. The operator's `init-uv` init
# container runs `uv sync --frozen` (above) to materialise the env onto the
# per-instance PVC, so uv must be on PATH at runtime — the resolved venv at
# /opt/venv does not include uv. Without this, init-uv exits 127
# ("uv: not found") and no HermesInstance reaches Ready. See #68.
COPY --from=uv /uv /uvx /usr/local/bin/
# UV_* defaults for the init-uv `uv sync` at pod start: pin a deterministic
# interpreter and a PVC-safe link mode so uv never tries to download a Python or
# hardlink across filesystems (image layer -> mounted PVC).
ENV PATH="/opt/venv/bin:${PATH}" \
PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
UV_PYTHON_DOWNLOADS=never \
UV_PYTHON=/usr/local/bin/python \
UV_LINK_MODE=copy \
HOME=/home/hermes
# Image metadata. The HERMES_VERSION label is the one the operator's autoupdate
# controller (Plan 5) compares against the registry tag.
LABEL org.opencontainers.image.title="hermes-agent" \
org.opencontainers.image.source="https://github.com/paperclipinc/hermes-operator" \
org.opencontainers.image.documentation="https://github.com/paperclipinc/hermes-operator/blob/main/images/hermes-agent/README.md" \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.vendor="paperclipinc" \
hermes.agent/version="${HERMES_VERSION}"
USER hermes
WORKDIR /home/hermes
COPY --chown=hermes:hermes entrypoint.sh /usr/local/bin/hermes-entrypoint
RUN chmod +x /usr/local/bin/hermes-entrypoint
# tini as PID 1 to reap any subprocess hermes-agent forks (audio transcoders,
# git operations, skill executors). `--` separates tini's flags from the wrapped
# entrypoint script.
ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/hermes-entrypoint"]
CMD ["serve"]