Files
PaperClipAI/docker/daytona-runner/Dockerfile
Dotta d593463ab6 perf(e2e): narrow Daytona image cache inputs (#12850)
## Thinking Path

> - Paperclip uses paid full-stack tests to verify local and Daytona
runner behavior.
> - Daytona tests reuse a content-addressed runner image when its
runtime inputs match.
> - The prior key covered the full runner package even when Docker
excluded development files.
> - Test-only and documentation changes could therefore force an
identical image rebuild.
> - This pull request aligns the Docker input closure and content-key
closure.
> - The benefit is faster paid-test iteration without unsafe image
reuse.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The Daytona paid-test workflow currently rebuilds its large runner image
after changes to runner tests, fixtures, smoke scripts, or
documentation. Those files do not enter the image and do not change its
runtime bytes.

**Subsystem affected**

The runner full-stack E2E workflow and its Daytona image build contract
are affected.

**Current behavior**

The content key hashes the full runner package. A development-only edit
changes the key even though the Docker build context excludes that edit.

**Proposed behavior**

The Dockerfile copies an explicit runtime build closure. The content key
hashes the same closure and continues to include every source, manifest,
lockfile, protocol, toolchain, and pinned image input that can affect
runtime bytes.

**Reason and benefit**

The workflow can reuse verified images for test-only changes. A runtime
change still creates a new immutable key and image.

**Breaking changes**

None. This changes only paid-test image cache identity and Docker build
inputs.

## What Changed

- Replace broad runner and eval package copies with explicit build
inputs.
- Advance the Daytona image content schema to version 5.
- Hash the matching explicit TypeScript, protocol, script, manifest,
lockfile, and Rust closure.
- Add contract coverage for runtime inputs and development-only
exclusions.

## Verification

- Focused Daytona image contract tests passed: 6 of 6.
- Exact-head ordinary CI [run
33913366909](https://github.com/paperclipai/paperclip/actions/runs/33913366909)
passed every job.
- The PR policy check passed on [run 33913366951, attempt
2](https://github.com/paperclipai/paperclip/actions/runs/33913366951).
- The one-cell paid [run
33916670340](https://github.com/paperclipai/paperclip/actions/runs/33916670340)
passed end to end.
- Image job 101165705592 built the explicit 6.33 MB context from exact
source revision `4bcfb3faa7694aad4ceca2193230d9693af6c9e0`.
- The workflow published content key
`3a3a8a19d2362263e972bead4427048c82a7da61dc203cd5c83aa40b88d90524` at
immutable digest
`sha256:a5b6f7517bc020ec2bae8075210d1a3f867284f4733042114528e19150ffac0a`.
- Cosign verified the image and recorded transparency log entry
2715972694.
- The sole `core-compatibility.legacy-codex.daytona.message-marker` cell
passed in job 101168063383.
- Campaign aggregation, immutable S3 history publication, and GitHub
Pages publication all passed.
- Full local test, build, and typecheck suites were not run.

## Risks

A future Docker build input could be omitted from the explicit closure.
Contract tests reject the prior broad copies and check the current
required runtime inputs. The real Daytona image build also qualified the
closure before merge.

## Model Used

OpenAI Codex GPT-5.6 with agentic reasoning and tool use.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links
- [x] My branch name describes the change and contains no internal
Paperclip ticket id
- [x] I have run focused tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-09-04 15:48:05 -05:00

135 lines
7.5 KiB
Docker

# syntax=docker/dockerfile:1.7@sha256:a57df69d0ea827fb7266491f2813635de6f17269be881f696fbfdf2d83dda33e
# Keep the Rust toolchain explicit so the runner artifact is reproducible and
# is always built for the same platform as the final Daytona image. Base-image
# digests are the reviewed linux/amd64 manifests; the tags are annotations.
FROM rust:1.97-bookworm@sha256:408fe88047cef61a2087653b0c5255fa51c0f2d6d94ddedd7a2562a9b91a46f6 AS runnerd-build
WORKDIR /workspace/packages/paperclip-runner/runner
COPY packages/paperclip-runner/runner/Cargo.toml packages/paperclip-runner/runner/Cargo.lock ./
COPY packages/paperclip-runner/runner/crates ./crates
COPY packages/paperclip-runner/protocol ../protocol
RUN cargo build --locked --release -p paperclip-runner-core --bin paperclip-runnerd \
&& strip /workspace/packages/paperclip-runner/runner/target/release/paperclip-runnerd
FROM node:24-bookworm@sha256:9137a20e25879e0b557227b57e3ee4e9af4bde29eb3db66134cd1723e84f830b AS provider-pack-build
ARG PAPERCLIP_RUNNER_SOURCE_REVISION
RUN test -n "${PAPERCLIP_RUNNER_SOURCE_REVISION}"
RUN corepack enable && corepack prepare pnpm@9.15.4 --activate
WORKDIR /workspace
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc tsconfig.base.json ./
COPY patches ./patches
COPY scripts/link-plugin-dev-sdk.mjs ./scripts/link-plugin-dev-sdk.mjs
COPY \
packages/paperclip-eval-kernel/package.json \
packages/paperclip-eval-kernel/tsconfig.json \
./packages/paperclip-eval-kernel/
COPY packages/paperclip-eval-kernel/src ./packages/paperclip-eval-kernel/src
COPY \
packages/paperclip-runner/package.json \
packages/paperclip-runner/styles.css \
packages/paperclip-runner/tsconfig.json \
packages/paperclip-runner/tsconfig.surfaces.json \
./packages/paperclip-runner/
COPY packages/paperclip-runner/protocol ./packages/paperclip-runner/protocol
COPY packages/paperclip-runner/runner/crates/runner-core/src/generated_acpx_sidecar_contract.rs ./packages/paperclip-runner/runner/crates/runner-core/src/generated_acpx_sidecar_contract.rs
COPY \
packages/paperclip-runner/scripts/acpx-sidecar-contract.mjs \
packages/paperclip-runner/scripts/build-provider-pack.mjs \
packages/paperclip-runner/scripts/build-verified-provider-entrypoints.mjs \
packages/paperclip-runner/scripts/generate-acpx-sidecar-contract.mjs \
packages/paperclip-runner/scripts/generate-protocol-schema-module.mjs \
./packages/paperclip-runner/scripts/
COPY packages/paperclip-runner/src ./packages/paperclip-runner/src
RUN pnpm install --frozen-lockfile --filter '@paperclipai/paperclip-runner...'
RUN pnpm --filter @paperclipai/paperclip-runner build:typescript \
&& PAPERCLIP_RUNNER_SOURCE_REVISION="${PAPERCLIP_RUNNER_SOURCE_REVISION}" \
node packages/paperclip-runner/scripts/build-provider-pack.mjs /provider-pack
# Fleet sandbox base image. Keep this section aligned with
# paperclipai/paperclip-cloud/fleet-sandbox-image/Dockerfile. The only Paperclip
# runner-specific addition is /usr/local/bin/paperclip-runnerd below.
FROM daytonaio/sandbox:0.8.0@sha256:eadf88e4391072b7ad4bed27d9cadfc9fe9d8ed375d9219d34c2ccb518f213e3
ARG PAPERCLIP_RUNNER_CONTENT_ID
ARG PAPERCLIP_RUNNER_SOURCE_REVISION
RUN test -n "${PAPERCLIP_RUNNER_CONTENT_ID}" \
&& test -n "${PAPERCLIP_RUNNER_SOURCE_REVISION}"
LABEL io.paperclip.runner.content-id="${PAPERCLIP_RUNNER_CONTENT_ID}" \
org.opencontainers.image.revision="${PAPERCLIP_RUNNER_SOURCE_REVISION}"
USER root
ENV PAPERCLIP_RUNNER_PROVIDER_PACK_ROOT=/opt/paperclip-runner/provider-pack
ENV PATH=${PAPERCLIP_RUNNER_PROVIDER_PACK_ROOT}/node_modules/.bin:/usr/local/share/nvm/current/bin:/usr/local/python/current/bin:/usr/local/py-utils/bin:${PATH}
RUN npm install -g \
@anthropic-ai/claude-code@2.1.19 \
@openai/codex@0.148.0 \
@xai-official/grok@1.0.3 \
@google/gemini-cli@0.56.0 \
@moonshot-ai/kimi-code@0.38.0 \
opencode-ai@1.18.17 \
&& npm cache clean --force
RUN pip install --no-cache-dir --break-system-packages hermes-agent==0.15.2
ARG CURSOR_VERSION=2026.08.11-e8db854
ARG CURSOR_SHA256_AMD64=bfff4bf6f4e9dd30c1d0ef0a70b6077b074015dd2948e4c50685d53afdcfce5a
RUN set -eu; \
arch="$(dpkg --print-architecture)"; \
[ "$arch" = "amd64" ] || { echo "FATAL: cursor pin only covers amd64, not $arch" >&2; exit 1; }; \
dir="/opt/cursor/versions/${CURSOR_VERSION}"; mkdir -p "$dir"; \
curl -fsSL "https://downloads.cursor.com/lab/${CURSOR_VERSION}/linux/x64/agent-cli-package.tar.gz" -o /tmp/cursor.tgz; \
printf '%s /tmp/cursor.tgz\n' "${CURSOR_SHA256_AMD64}" > /tmp/cursor.sha256; \
sha256sum -c /tmp/cursor.sha256; \
tar --strip-components=1 -xzf /tmp/cursor.tgz -C "$dir"; \
rm -f /tmp/cursor.tgz /tmp/cursor.sha256; \
ln -sf "$dir/cursor-agent" /usr/local/bin/cursor-agent; \
ln -sf /usr/local/bin/cursor-agent /usr/local/bin/agent; \
chmod -R a+rX /opt/cursor
ARG GH_VERSION=2.98.0
ARG GH_SHA256_AMD64=3b8ac6b30336802fc1a858d7c084e11cdf24ac1a761ca90b68022d7d729208de
RUN set -eu; \
arch="$(dpkg --print-architecture)"; \
[ "$arch" = "amd64" ] || { echo "FATAL: gh pin only covers amd64, not $arch" >&2; exit 1; }; \
curl -fsSL "https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_amd64.tar.gz" -o /tmp/gh.tgz; \
printf '%s /tmp/gh.tgz\n' "${GH_SHA256_AMD64}" > /tmp/gh.sha256; \
sha256sum -c /tmp/gh.sha256; \
tar -xzf /tmp/gh.tgz -C /tmp; \
install "/tmp/gh_${GH_VERSION}_linux_amd64/bin/gh" /usr/local/bin/gh; \
rm -rf /tmp/gh.tgz /tmp/gh.sha256 "/tmp/gh_${GH_VERSION}_linux_amd64"
COPY --from=runnerd-build /workspace/packages/paperclip-runner/runner/target/release/paperclip-runnerd /usr/local/bin/paperclip-runnerd
COPY --from=provider-pack-build /provider-pack /opt/paperclip-runner/provider-pack
RUN set -eu; \
chmod -R a+rX /opt/paperclip-runner/provider-pack; \
printf '%s\n' 'export PATH=/opt/paperclip-runner/provider-pack/node_modules/.bin:$PATH' \
> /etc/profile.d/01-paperclip-runner-provider-pack.sh; \
chmod 0644 /etc/profile.d/01-paperclip-runner-provider-pack.sh; \
for command_name in acpx claude-agent-acp codex-acp claude codex grok gemini kimi opencode cursor-agent agent hermes gh paperclip-runnerd; do \
command -v "$command_name" >/dev/null || { echo "FATAL: $command_name not on PATH after build" >&2; exit 1; }; \
done; \
metadata="$(paperclip-runnerd --build-metadata)"; \
/opt/paperclip-runner/provider-pack/node_modules/node/bin/node -e "const [major,minor,patch]=process.versions.node.split('.').map(Number); if (major<24 || (major===24 && (minor<11 || (minor===11 && patch<0)))) process.exit(1)"; \
test -f /opt/paperclip-runner/provider-pack/provider-pack.json; \
printf '%s' "$metadata" | grep -q '"dial_ws_loopback"'; \
printf '%s' "$metadata" | grep -q '"dial_wss"'; \
printf '%s' "$metadata" | grep -q '"listen_ws"'; \
echo "all agent CLIs and runnerd transport modes are available"
USER daytona
RUN /bin/sh -lc 'set -eu; \
test -r /opt/paperclip-runner/provider-pack/provider-pack.json; \
/opt/paperclip-runner/provider-pack/node_modules/node/bin/node -e \
"JSON.parse(require(\"node:fs\").readFileSync(\"/opt/paperclip-runner/provider-pack/provider-pack.json\", \"utf8\"))"; \
for command_name in acpx claude-agent-acp codex-acp; do \
command -v "$command_name" >/dev/null || { echo "FATAL: $command_name not on PATH for daytona user" >&2; exit 1; }; \
done; \
test "$(acpx --version)" = "0.13.1"; \
test "$(claude-agent-acp --version)" = "0.70.0"; \
test "$(codex-acp --version)" = "@agentclientprotocol/codex-acp 1.6.2"'