9802636be3
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work, and it runs agents through pluggable adapters. > - One of those adapters, `gemini_local` (`packages/adapters/gemini-local/`), runs Google's Gemini CLI on the same host as the server. > - For local (in-container) execution, the adapter only probes `PATH` for the binary (`packages/adapter-utils/src/execution-target.ts`); it does **not** auto-install — only `sandbox` transport targets install on demand via `SANDBOX_INSTALL_COMMAND`. > - The production Docker image bakes in `claude`, `codex`, and `opencode` so their `*_local` adapters work out of the box, but `gemini` was never added — so `gemini_local` fails inside the container with a missing-binary error. > - This PR adds `@google/gemini-cli@latest` to the image's global install so `gemini_local` works locally like the other bundled CLIs, sets `GEMINI_SANDBOX=false` for safe in-container CLI use, and documents all bundled CLIs. > - The benefit is plug-and-play Gemini support in Docker with no per-deployment CLI install step, plus accurate docs (including a previously-undocumented `opencode` bundle) and a heads-up about Google's imminent unrestricted-API-key block. ## Linked Issues or Issue Description No existing issue. Following the **adapter/feature** template fields: - **Capability:** Run the existing `gemini_local` adapter inside the official Docker image without a manual CLI install. - **Problem/motivation:** The image pre-installs `claude`, `codex`, and `opencode`, but not `gemini`. Because local execution probes `PATH` and never auto-installs (only sandbox targets do), `gemini_local` runs fail in-container with a missing-binary error. Operators currently have to maintain a forked image. - **Proposed solution:** Add `@google/gemini-cli@latest` to the existing global `npm install` line; set `GEMINI_SANDBOX=false` in `ENV`. - **Alternatives considered:** Runtime install at container start (slower, network-dependent, inconsistent with how the other three CLIs are handled). **Related / superseded PRs** (both make the same Dockerfile change and can be closed if this merges): - #5912 — `fix(docker): install gemini-cli and normalize persistent volume permissions`. Same Dockerfile line, but also bundles unrelated volume-permission and heartbeat-test changes, and a maintainer noted it now has a merge conflict. I corrected Greptile's `GEMINI_SANDBOX` reasoning there and it was re-scored 5/5 (see [resolved thread](https://github.com/paperclipai/paperclip/pull/5912#discussion_r3367332785)) — this PR is the rebased, docs-complete, single-purpose version of that change. - #2731 — `Added @google/gemini-cli to the Dockerfile`. One-liner, now stale and merge-conflicting (predates the `opencode-ai` addition). > **Note for Greptile:** the `--sandbox=none` reasoning for the Gemini CLI sandbox concern was already validated on #5912's `Dockerfile:59` thread and accepted (re-scored 5/5). This PR additionally bakes `GEMINI_SANDBOX=false` into `ENV` as defense-in-depth, so the concern is covered both at the adapter layer and the image layer. ## What Changed - **Dockerfile:** add `@google/gemini-cli@latest` to the production global npm install (after `opencode-ai`). - **Dockerfile:** add `GEMINI_SANDBOX=false` to the `ENV` block (matches the existing `OPENCODE_ALLOW_ALL_MODELS=true` precedent) so a manually-invoked `gemini` inside the container does not attempt a Docker-in-Docker sandbox. - **docs/deploy/docker.md:** rename the section to "Local Adapter CLIs in Docker"; list all four bundled CLIs mapped to their adapter type keys (fixes a pre-existing gap — `opencode` was bundled but undocumented); add `GEMINI_API_KEY` to the example; document per-provider credentials, the `GEMINI_SANDBOX=false` default, and Google's 2026-06-19 unrestricted-key block with the `gemini auth login` (OAuth) alternative. ## Verification - `npm view @google/gemini-cli` confirms the package exists, provides the `gemini` bin, and requires Node `>=20` (the base image is Node 22 LTS). ✅ - Adapter already disables the CLI sandbox per run: `packages/adapters/gemini-local/src/server/execute.ts` pushes `--sandbox=none` whenever `config.sandbox` is false (the default). ✅ - Confirmed neither file was modified on `upstream/master`, so this rebases cleanly with no conflicts. ✅ - Full image build is exercised by CI. (I did not run the multi-stage `docker build` locally; the change adds one package to an existing, working `npm install` line.) - Reviewer manual check: `docker build -t paperclip-local . && docker run --rm paperclip-local gemini --version` should print the CLI version. ## Risks - **Low risk.** Adds one npm package to an existing global install and one inert env var; no application code paths change. - Minor image-size increase from the additional CLI (consistent with the three already bundled). - `@latest` is unpinned — intentionally consistent with the sibling `@anthropic-ai/claude-code@latest` / `@openai/codex@latest` on the same line; pinning all of them is a separate decision out of scope here. ## Model Used Claude Opus 4.8 (model ID `claude-opus-4-8`, 1M-context variant), via Claude Code with tool use / agentic file editing and web research. ## 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 OR (b) described the issue in-PR following the relevant issue template - [ ] I have run tests locally and they pass (no unit tests cover the Dockerfile; package/bin/engine verified via `npm view`, full build runs in CI) - [ ] I have added or updated tests where applicable (N/A — Docker image + docs change) - [ ] If this change affects the UI, I have included before/after screenshots (N/A) - [x] 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 (pending CI run on this PR) - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups (pending review) - [x] I will address all Greptile and reviewer comments before requesting merge Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
91 lines
3.7 KiB
Docker
91 lines
3.7 KiB
Docker
# syntax=docker/dockerfile:1.20
|
|
FROM node:lts-trixie-slim AS base
|
|
ARG USER_UID=1000
|
|
ARG USER_GID=1000
|
|
RUN apt-get update \
|
|
&& apt-get install -y --no-install-recommends ca-certificates gosu curl gh git wget ripgrep python3 \
|
|
&& rm -rf /var/lib/apt/lists/* \
|
|
&& corepack enable
|
|
|
|
# Modify the existing node user/group to have the specified UID/GID to match host user
|
|
RUN usermod -u $USER_UID --non-unique node \
|
|
&& groupmod -g $USER_GID --non-unique node \
|
|
&& usermod -g $USER_GID -d /paperclip node
|
|
|
|
FROM base AS deps
|
|
WORKDIR /app
|
|
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml .npmrc ./
|
|
COPY cli/package.json cli/
|
|
COPY server/package.json server/
|
|
COPY ui/package.json ui/
|
|
COPY packages/shared/package.json packages/shared/
|
|
COPY packages/db/package.json packages/db/
|
|
COPY packages/adapter-utils/package.json packages/adapter-utils/
|
|
COPY packages/mcp-server/package.json packages/mcp-server/
|
|
COPY packages/skills-catalog/package.json packages/skills-catalog/
|
|
COPY packages/teams-catalog/package.json packages/teams-catalog/
|
|
COPY packages/adapters/acpx-local/package.json packages/adapters/acpx-local/
|
|
COPY packages/adapters/claude-local/package.json packages/adapters/claude-local/
|
|
COPY packages/adapters/codex-local/package.json packages/adapters/codex-local/
|
|
COPY packages/adapters/cursor-cloud/package.json packages/adapters/cursor-cloud/
|
|
COPY packages/adapters/cursor-local/package.json packages/adapters/cursor-local/
|
|
COPY packages/adapters/gemini-local/package.json packages/adapters/gemini-local/
|
|
COPY packages/adapters/grok-local/package.json packages/adapters/grok-local/
|
|
COPY packages/adapters/openclaw-gateway/package.json packages/adapters/openclaw-gateway/
|
|
COPY packages/adapters/opencode-local/package.json packages/adapters/opencode-local/
|
|
COPY packages/adapters/pi-local/package.json packages/adapters/pi-local/
|
|
COPY packages/plugins/sdk/package.json packages/plugins/sdk/
|
|
COPY --parents packages/plugins/sandbox-providers/./*/package.json packages/plugins/sandbox-providers/
|
|
COPY packages/plugins/paperclip-plugin-fake-sandbox/package.json packages/plugins/paperclip-plugin-fake-sandbox/
|
|
COPY packages/plugins/plugin-llm-wiki/package.json packages/plugins/plugin-llm-wiki/
|
|
COPY packages/plugins/plugin-workspace-diff/package.json packages/plugins/plugin-workspace-diff/
|
|
COPY patches/ patches/
|
|
COPY scripts/link-plugin-dev-sdk.mjs scripts/
|
|
|
|
RUN pnpm install --frozen-lockfile
|
|
|
|
FROM base AS build
|
|
WORKDIR /app
|
|
COPY --from=deps /app /app
|
|
COPY . .
|
|
RUN pnpm --filter @paperclipai/ui build
|
|
RUN pnpm --filter @paperclipai/plugin-sdk build
|
|
RUN pnpm --filter @paperclipai/server build
|
|
RUN test -f server/dist/index.js || (echo "ERROR: server build output missing" && exit 1)
|
|
|
|
FROM base AS production
|
|
ARG USER_UID=1000
|
|
ARG USER_GID=1000
|
|
WORKDIR /app
|
|
COPY --chown=node:node --from=build /app /app
|
|
RUN npm install --global --omit=dev @anthropic-ai/claude-code@latest @openai/codex@latest opencode-ai @google/gemini-cli@latest \
|
|
&& apt-get update \
|
|
&& apt-get install -y --no-install-recommends openssh-client jq \
|
|
&& rm -rf /var/lib/apt/lists/* \
|
|
&& mkdir -p /paperclip \
|
|
&& chown node:node /paperclip
|
|
|
|
COPY scripts/docker-entrypoint.sh /usr/local/bin/
|
|
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
|
|
|
ENV NODE_ENV=production \
|
|
HOME=/paperclip \
|
|
HOST=0.0.0.0 \
|
|
PORT=3100 \
|
|
SERVE_UI=true \
|
|
PAPERCLIP_HOME=/paperclip \
|
|
PAPERCLIP_INSTANCE_ID=default \
|
|
USER_UID=${USER_UID} \
|
|
USER_GID=${USER_GID} \
|
|
PAPERCLIP_CONFIG=/paperclip/instances/default/config.json \
|
|
PAPERCLIP_DEPLOYMENT_MODE=authenticated \
|
|
PAPERCLIP_DEPLOYMENT_EXPOSURE=private \
|
|
OPENCODE_ALLOW_ALL_MODELS=true \
|
|
GEMINI_SANDBOX=false
|
|
|
|
VOLUME ["/paperclip"]
|
|
EXPOSE 3100
|
|
|
|
ENTRYPOINT ["docker-entrypoint.sh"]
|
|
CMD ["node", "--import", "./server/node_modules/tsx/dist/loader.mjs", "server/dist/index.js"]
|