Files
clawmates/deploy/clawmates-runtime/Dockerfile
Omar SobhandClaude Opus 5 9f874bc06a feat(runtime): install the toolchain missions are told to use
`templates/teams/rust_sdlc.toml` instructs the coder to run `cargo test`; the
`done_when` evaluator runs a project's own suite to verify a claim rather than
believe it; `security_scan.rs` shells out to cargo-audit, gitleaks, trivy and
semgrep. The runtime image contained none of them.

The security consequence was the worse one. With no scanners present, a scan
emitted four `<tool>:tool_error` task rows and completed — a scan that scanned
nothing and reported cleanly. Same class of false signal as a verifier that
never ran a command.

Adds gitleaks 8.30.1, trivy 0.72.0, semgrep (in its own venv so its pinned
dependency tree cannot collide), and a minimal Rust stable toolchain with
cargo-audit. Versions are pinned as build args and were taken from the
releases API — the first attempt used plausible-looking numbers that 404'd.

Layers are ordered cheapest-and-most-stable first so bumping a scanner does
not invalidate the Rust layer, and the cargo registry is dropped after
`cargo install`.

Measured: 864 MB -> 3.13 GB (scanners +350 MB, Rust +1.23 GB, semgrep
+680 MB). Note this image is NOT in `AGENT_IMAGES` — it never ships to fleet
nodes, only gw-04 holds it, against 112 GB free. An earlier note claiming
otherwise was wrong. The real cost is a slower `docker save | load` per
rebuild.

Verified in the built image: rustc 1.97.1, cargo-audit 0.22.2, gitleaks
8.30.1, trivy 0.72.0, semgrep 1.172.0, python 3.11.2, plus the existing git,
claude and node.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-01 18:43:53 -07:00

123 lines
6.2 KiB
Docker

# Slim Clawmates runtime = the zeroclaw daemon (gateway API), API-only.
# Simple full-source build (avoids the upstream Dockerfile's stale manifest-
# prefetch step). web/dist may be empty → dashboard is omitted, /api/* works.
# Requires BuildKit (cache mounts). Build context = the zeroclaw source tree.
# syntax=docker/dockerfile:1
# bookworm-pinned so the binary's glibc matches the bookworm runtime stage
FROM rust:1.96-slim-bookworm AS build
RUN apt-get update && apt-get install -y --no-install-recommends \
pkg-config build-essential cmake libssl-dev ca-certificates git \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY . .
# Ensure the gateway's include_dir!("../../web/dist") target exists at compile time.
RUN mkdir -p web/dist
RUN --mount=type=cache,id=cm-cargo-registry,target=/usr/local/cargo/registry,sharing=locked \
--mount=type=cache,id=cm-cargo-git,target=/usr/local/cargo/git,sharing=locked \
--mount=type=cache,id=cm-target-bookworm,target=/app/target,sharing=locked \
cargo build --release --locked --bin zeroclaw \
&& cp target/release/zeroclaw /usr/local/bin/zeroclaw
FROM debian:bookworm-slim
# ca-certificates for TLS; Node 22 (Kimi Code needs >=22.19; Claude Code is fine
# on it too) to install BOTH agent CLIs that the subprocess providers spawn:
# `claude -p` (claude_cli, Claude subscription) and `kimi -p` (kimi_cli, Kimi
# membership) — subscription-backed, no per-minute API TPM ceiling.
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl gnupg git \
&& curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
&& apt-get install -y --no-install-recommends nodejs \
&& npm install -g @anthropic-ai/claude-code @moonshot-ai/kimi-code \
&& npm cache clean --force \
&& rm -rf /var/lib/apt/lists/* /root/.npm
# Upstream Gitea SDLC tooling for team-scoped agents that operate on
# git.redclaw.dev. `tea` covers shell-level ops (git remote helpers,
# clone/push, PR checkout); `gitea-mcp` gives the LLM a structured
# MCP tool surface (create_pr, list_repo_issues, create_file, etc.).
# Both are official upstream binaries — no custom bundle to maintain.
ARG TEA_VERSION=0.14.2
ARG GITEA_MCP_VERSION=1.3.0
RUN set -eux; \
arch="$(dpkg --print-architecture)"; \
case "$arch" in \
amd64) mcp_asset="Linux_x86_64" ;; \
arm64) mcp_asset="Linux_arm64" ;; \
*) echo "unsupported arch: $arch"; exit 1 ;; \
esac; \
curl -fsSL "https://dl.gitea.com/tea/${TEA_VERSION}/tea-${TEA_VERSION}-linux-${arch}" \
-o /usr/local/bin/tea && chmod +x /usr/local/bin/tea; \
tmp="$(mktemp -d)" && \
curl -fsSL "https://gitea.com/gitea/gitea-mcp/releases/download/v${GITEA_MCP_VERSION}/gitea-mcp_${mcp_asset}.tar.gz" \
-o "$tmp/gitea-mcp.tgz" && \
tar -xzf "$tmp/gitea-mcp.tgz" -C "$tmp" && \
install -m 0755 "$tmp/gitea-mcp" /usr/local/bin/gitea-mcp && \
rm -rf "$tmp"; \
/usr/local/bin/tea --version | head -1; \
/usr/local/bin/gitea-mcp --version 2>&1 | head -1 || true
# ── Mission toolchain ────────────────────────────────────────────────
# Agents and the phase evaluator both run project checks inside this image:
# `templates/teams/rust_sdlc.toml` tells the coder to run `cargo test`, the
# `done_when` evaluator runs the project's own suite to verify a claim rather
# than believe it, and `security_scan.rs` shells out to four scanners.
#
# None of it was here. A Rust mission's `cargo build` failed, and every
# security scan produced four `<tool>:tool_error` task rows instead of
# findings — a scan that scanned nothing and reported cleanly.
#
# Measured cost on top of the 864 MB base: scanners +350 MB, Rust +1.23 GB,
# semgrep +680 MB. This image is NOT in `AGENT_IMAGES`, so it never ships to
# fleet nodes — only gw-04 holds it, against 112 GB free. The real cost is a
# slower `docker save | load` on each runtime rebuild, which is worth paying
# for missions that can actually compile and test what they write.
#
# Ordered cheapest-and-most-stable first so a version bump lower down doesn't
# invalidate the expensive layers above it.
ARG GITLEAKS_VERSION=8.30.1
ARG TRIVY_VERSION=0.72.0
RUN set -eux; \
arch="$(dpkg --print-architecture)"; \
case "$arch" in \
amd64) gl_arch=x64; tv_arch=64bit ;; \
arm64) gl_arch=arm64; tv_arch=ARM64 ;; \
*) echo "unsupported arch: $arch"; exit 1 ;; \
esac; \
curl -fsSL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_${gl_arch}.tar.gz" \
| tar -xz -C /usr/local/bin gitleaks; \
curl -fsSL "https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-${tv_arch}.tar.gz" \
| tar -xz -C /usr/local/bin trivy; \
gitleaks version; trivy --version | head -1
# semgrep in its own venv so its pinned dependency tree can never collide with
# anything else installed here.
RUN apt-get update && apt-get install -y --no-install-recommends \
python3 python3-pip python3-venv \
&& python3 -m venv /opt/semgrep \
&& /opt/semgrep/bin/pip install --no-cache-dir semgrep \
&& ln -s /opt/semgrep/bin/semgrep /usr/local/bin/semgrep \
&& rm -rf /var/lib/apt/lists/* \
&& semgrep --version
# Rust last: the largest layer and the one most likely to be bumped, so it
# sits where a rebuild costs the least cache.
ENV RUSTUP_HOME=/usr/local/rustup \
CARGO_HOME=/usr/local/cargo \
PATH=/usr/local/cargo/bin:$PATH
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc libc6-dev pkg-config libssl-dev make \
&& curl -fsSL https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain stable \
&& cargo install cargo-audit --locked --no-default-features \
&& rm -rf /var/lib/apt/lists/* "$CARGO_HOME/registry" "$CARGO_HOME/git" \
&& chmod -R a+rX "$RUSTUP_HOME" "$CARGO_HOME" \
&& rustc --version && cargo audit --version
COPY --from=build /usr/local/bin/zeroclaw /usr/local/bin/zeroclaw
ENV HOME=/zeroclaw-data \
ZEROCLAW_WORKSPACE=/zeroclaw-data/workspace \
ZEROCLAW_GATEWAY_PORT=42617
RUN mkdir -p /zeroclaw-data/workspace
EXPOSE 42617
ENTRYPOINT ["zeroclaw"]
CMD ["daemon"]