# syntax=docker/dockerfile:1.7 # # AgentOS runner image for the VDS Compose topology. # # This image is distinct from the production Flue image (Dockerfile): it is the # dedicated runner that registers with the Rivet Engine and owns the persistent # host filesystem used to clone repositories, install dependencies, and mount # isolated checkouts into AgentOS workspaces. It needs Bun + Git + the engine-cli # used by local development, plus Node (required by the Flue/AgentOS runtime). # # The runner is internal-only. It never publishes a port; only the engine and # Compose healthcheck reach it. Secrets are injected at runtime via the Compose # environment, never baked into a layer. # # RIVET_RUNNER_VERSION is a build-time contract: it lets the engine route new # actors to the new runner and drain old ones. CI sets it to the release # identity (commit SHA or build timestamp) for every immutable image it pushes. FROM oven/bun:1.3.14 AS bun FROM node:24-bookworm-slim AS build # Bun is needed to install dependency trees in the cloned workspaces. COPY --from=bun /usr/local/bin/bun /usr/local/bin/bun WORKDIR /app # Native build toolchain for optional native deps during install. RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ g++ \ make \ python3 \ && rm -rf /var/lib/apt/lists/* # pnpm-lock.yaml is authoritative; Bun would migrate it and reject frozen mode. RUN npm install --global pnpm@11.17.0 COPY . . RUN pnpm install --frozen-lockfile # Runner image only. No Flue server build is needed: the runner entry point # (src/runner.ts) calls runtimeRegistry.startAndWait() to register with the # engine. Building the Flue Node server here would be dead weight. FROM node:24-bookworm-slim AS runner # Git and CA certs are required by RepositoryWorkspace to clone user repos and # install dependencies inside the mounted source mirror. RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ git \ openssh-client \ && rm -rf /var/lib/apt/lists/* \ && git --version # Carry Bun into the runtime image so `bun install --frozen-lockfile` works on # cloned checkouts and BUN_EXECUTABLE resolves to a known path. COPY --from=bun /usr/local/bin/bun /usr/local/bin/bun ARG RIVET_RUNNER_VERSION ENV RIVET_RUNNER_VERSION=${RIVET_RUNNER_VERSION} ENV NODE_ENV=production WORKDIR /app COPY --from=build /app /app # Non-root runtime. Compose mounts the workspace bind directory initialized for # this UID so the process can serve its isolated worktrees. RUN groupadd --system --gid 10001 zopu \ && useradd --system --uid 10001 --gid zopu --create-home --home-dir /home/zopu zopu \ && mkdir -p /var/lib/zopu/workspaces \ && chown -R zopu:zopu /var/lib/zopu /home/zopu # Point the runtime at the Bun executable discovered by RepositoryWorkspace. # /home/zopu is the pi agent home mount target used by attempt-runner.ts. ENV BUN_EXECUTABLE=/usr/local/bin/bun \ AGENT_WORKSPACE_ROOT=/var/lib/zopu/workspaces USER zopu # runner.ts is TypeScript and this repository executes it with Bun locally. CMD ["bun", "packages/agents/src/runner.ts"]