89 lines
4.0 KiB
Docker
89 lines
4.0 KiB
Docker
|
|
# syntax=docker/dockerfile:1.7
|
||
|
|
FROM python:3.13-slim
|
||
|
|
|
||
|
|
ARG GIT_SHA=unknown
|
||
|
|
ENV WORKDIR=/workdir
|
||
|
|
|
||
|
|
RUN groupadd -g 1000 model && \
|
||
|
|
useradd -m -u 1000 -g 1000 model && \
|
||
|
|
gpasswd -d model root || true
|
||
|
|
|
||
|
|
RUN apt-get update && \
|
||
|
|
apt-get install -y --no-install-recommends \
|
||
|
|
awscli sudo curl zip unzip poppler-utils imagemagick && \
|
||
|
|
apt-get clean
|
||
|
|
|
||
|
|
RUN pip install --no-cache-dir \
|
||
|
|
uv==0.11.8 \
|
||
|
|
pdf2image==1.16.3 \
|
||
|
|
pandas==2.2.3 \
|
||
|
|
numpy==2.4.3
|
||
|
|
|
||
|
|
WORKDIR /app
|
||
|
|
|
||
|
|
# Deps layer: copy only manifests/lockfiles so `uv sync` stays cached when
|
||
|
|
# source changes. The list below is explicit by design —
|
||
|
|
# `just check-dockerfile-manifests` (also run in CI lint) fails if a new
|
||
|
|
# package gets added without being listed here. All packages are Python
|
||
|
|
# (jira/slack were ported from TS).
|
||
|
|
COPY pyproject.toml uv.lock /app/
|
||
|
|
COPY packages/core/pyproject.toml /app/packages/core/
|
||
|
|
COPY packages/google_calendar/pyproject.toml /app/packages/google_calendar/
|
||
|
|
COPY packages/google_mail/pyproject.toml /app/packages/google_mail/
|
||
|
|
COPY packages/jira/pyproject.toml /app/packages/jira/
|
||
|
|
COPY packages/mcp_proxy/pyproject.toml /app/packages/mcp_proxy/
|
||
|
|
COPY packages/read_file_safe/pyproject.toml /app/packages/read_file_safe/
|
||
|
|
COPY packages/shopify/pyproject.toml /app/packages/shopify/
|
||
|
|
COPY packages/slack/pyproject.toml /app/packages/slack/
|
||
|
|
COPY packages/syntara/pyproject.toml /app/packages/syntara/
|
||
|
|
|
||
|
|
RUN --mount=type=cache,target=/root/.cache/uv \
|
||
|
|
uv sync --locked --no-install-workspace --all-extras
|
||
|
|
|
||
|
|
# Source layer.
|
||
|
|
COPY . /app
|
||
|
|
|
||
|
|
# Finalize workspace package install. External deps were cached above, so this
|
||
|
|
# step only links editable workspace packages and is fast.
|
||
|
|
RUN --mount=type=cache,target=/root/.cache/uv \
|
||
|
|
uv sync --locked --all-packages --all-extras
|
||
|
|
|
||
|
|
# OpenHands runner: a single file that must be staged into the build context at
|
||
|
|
# openhands-runner/openhands_runner.py before `docker build` runs (it lands at
|
||
|
|
# /app/openhands-runner/openhands_runner.py via the source-layer COPY above).
|
||
|
|
# The Harbor OpenHandsAgent execs this runner in-container, so the image carries
|
||
|
|
# it with openhands-sdk in an isolated venv (kept separate from /app/.venv so its
|
||
|
|
# deps never collide with the MCP env).
|
||
|
|
RUN test -f /app/openhands-runner/openhands_runner.py || { \
|
||
|
|
echo "openhands_runner.py missing from build context —" \
|
||
|
|
"stage it into openhands-runner/ before building" >&2; exit 1; }
|
||
|
|
# This isolated, eval-only runner venv is not bound by the repo-wide
|
||
|
|
# `[tool.uv] exclude-newer` supply-chain cutoff (docker/pyproject.toml, ~14 days
|
||
|
|
# back): openhands-sdk and its deps (litellm, etc.) release continuously and are
|
||
|
|
# newer than the cutoff. Override it wholesale for just this install with a fixed
|
||
|
|
# date so resolution is reproducible. Bump the date whenever you bump the pin.
|
||
|
|
RUN --mount=type=cache,target=/root/.cache/uv \
|
||
|
|
uv venv /app/openhands-runner/.venv --python 3.13 && \
|
||
|
|
uv pip install --python /app/openhands-runner/.venv/bin/python \
|
||
|
|
--exclude-newer 2026-06-17T00:00:00Z \
|
||
|
|
openhands-sdk==1.28.1
|
||
|
|
|
||
|
|
# Verifier deps: tests/test.sh pip-installs these at runtime; baking them in
|
||
|
|
# makes the verifier fast and network-independent.
|
||
|
|
RUN pip install --no-cache-dir openpyxl pdfplumber python-docx
|
||
|
|
RUN mkdir -p ${WORKDIR} && chown -R model:model ${WORKDIR}
|
||
|
|
|
||
|
|
# Lock down sibling MCP-server source from the model user (uid 1000). syntara
|
||
|
|
# is the only server that runs as model; the rest run as root and never need
|
||
|
|
# to be readable by the agent. syntara eagerly imports its full dependency
|
||
|
|
# tree (incl. read_file_safe) before drop_privileges, so nothing else under
|
||
|
|
# /app/packages needs to stay readable post-setuid. /app/packages itself stays
|
||
|
|
# traversable so agents get a clean Permission denied rather than "directory
|
||
|
|
# doesn't exist".
|
||
|
|
RUN find /app/packages -mindepth 1 -maxdepth 1 -type d ! -name syntara \
|
||
|
|
-exec chown -R root:root {} + -exec chmod -R go-rwx {} +
|
||
|
|
|
||
|
|
RUN echo "$GIT_SHA" > /app/.git-sha
|
||
|
|
|
||
|
|
WORKDIR ${WORKDIR}
|