# Sandcastle sandbox image — runs the implementer + reviewer + decomposer # agents. Shape required by @ai-hero/sandcastle: a non-root `agent` user # (UID/GID aligned with the host so bind-mounted files share owner), Claude # Code CLI on PATH, and a long-running ENTRYPOINT so the container survives # the gap between sandcastle creating it and exec'ing into it. # # Authenticates via the host's mounted ~/.claude/ session (subscription # mode — sandcastle issue #191 workaround, our primary flow). Falls back # to ANTHROPIC_API_KEY when no host credentials are present. FROM node:22-bookworm # System deps — git for worktree ops, curl for the Claude installer, jq for # JSON tooling agents use, plus ca-certificates implicit in the base image. RUN apt-get update && apt-get install -y --no-install-recommends \ git \ curl \ jq \ && rm -rf /var/lib/apt/lists/* # pnpm via corepack (matches the repo's packageManager version). RUN corepack enable && corepack prepare pnpm@9 --activate # Build-args for UID/GID alignment: `sandcastle docker build-image` passes # the host user's UID/GID by default so image-built files and bind-mounted # files share an owner without runtime chown. ARG AGENT_UID=1000 ARG AGENT_GID=1000 # Rename the base image's "node" user to "agent" and align UID/GID. # `-o` (non-unique) is required because the host's GID may collide with a # pre-existing system group in the base image (e.g. macOS UID:501 GID:20 # collides with Debian's `dialout` group at GID 20). Allowing a duplicate # GID is safe here — only one user occupies the sandbox. RUN groupmod -o -g $AGENT_GID node && \ usermod -o -u $AGENT_UID -g $AGENT_GID -d /home/agent -m -l agent node USER ${AGENT_UID}:${AGENT_GID} # Claude Code CLI — used by sandcastle's claudeCode() agent provider. # The CLI reads credentials from ~/.claude/ inside the container; the host # mounts its ~/.claude/ over that path at sandbox start. RUN curl -fsSL https://claude.ai/install.sh | bash ENV PATH="/home/agent/.local/bin:$PATH" WORKDIR /home/agent # In worktree sandbox mode, sandcastle bind-mounts the git worktree at # ${SANDBOX_REPO_DIR} and overrides the working directory to that path at # container start. The Dockerfile's WORKDIR is just the default home. ENTRYPOINT ["sleep", "infinity"]