From 420cd4fd8dd555db44d48268f159856b90fb3182 Mon Sep 17 00:00:00 2001 From: "Cody (Radius Red)" Date: Wed, 1 Apr 2026 11:06:37 +0000 Subject: [PATCH] chore(docker): improve base image and organize docker files - Add wget, ripgrep, python3, and GitHub CLI (gh) to base image - Add OPENCODE_ALLOW_ALL_MODELS=true to production ENV - Move compose files, onboard-smoke Dockerfile to docker/ - Move entrypoint script to scripts/docker-entrypoint.sh - Add Podman Quadlet unit files (pod, app, db containers) - Add docker/README.md with build, compose, and quadlet docs - Add scripts/docker-build-test.sh for local build validation - Update all doc references for new file locations - Keep main Dockerfile at project root (no .dockerignore changes needed) Co-Authored-By: Paperclip --- Dockerfile | 20 ++- doc/DEVELOPING.md | 2 +- doc/DOCKER.md | 132 +++++++++++++++++- doc/UNTRUSTED-PR-REVIEW.md | 10 +- .../Dockerfile.onboard-smoke | 0 .../docker-compose.quickstart.yml | 4 +- .../docker-compose.untrusted-review.yml | 2 +- .../docker-compose.yml | 4 +- docker/quadlet/paperclip-db.container | 20 +++ docker/quadlet/paperclip.container | 23 +++ docker/quadlet/paperclip.pod | 3 + docs/deploy/docker.md | 8 +- scripts/docker-build-test.sh | 46 ++++++ .../docker-entrypoint.sh | 0 scripts/docker-onboard-smoke.sh | 2 +- 15 files changed, 249 insertions(+), 27 deletions(-) rename Dockerfile.onboard-smoke => docker/Dockerfile.onboard-smoke (100%) rename docker-compose.quickstart.yml => docker/docker-compose.quickstart.yml (86%) rename docker-compose.untrusted-review.yml => docker/docker-compose.untrusted-review.yml (97%) rename docker-compose.yml => docker/docker-compose.yml (94%) create mode 100644 docker/quadlet/paperclip-db.container create mode 100644 docker/quadlet/paperclip.container create mode 100644 docker/quadlet/paperclip.pod create mode 100755 scripts/docker-build-test.sh rename docker-entrypoint.sh => scripts/docker-entrypoint.sh (100%) diff --git a/Dockerfile b/Dockerfile index bf1501aad8..d613c380b4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,10 +2,17 @@ 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 curl git gosu \ - && rm -rf /var/lib/apt/lists/* - -RUN corepack enable + && apt-get install -y --no-install-recommends ca-certificates gosu curl git wget ripgrep python3 \ + && mkdir -p -m 755 /etc/apt/keyrings \ + && wget -nv -O/etc/apt/keyrings/githubcli-archive-keyring.gpg https://cli.github.com/packages/githubcli-archive-keyring.gpg \ + && echo "20e0125d6f6e077a9ad46f03371bc26d90b04939fb95170f5a1905099cc6bcc0 /etc/apt/keyrings/githubcli-archive-keyring.gpg" | sha256sum -c - \ + && chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \ + && mkdir -p -m 755 /etc/apt/sources.list.d \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" > /etc/apt/sources.list.d/github-cli.list \ + && apt-get update \ + && apt-get install -y --no-install-recommends gh \ + && 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 \ @@ -51,7 +58,7 @@ RUN npm install --global --omit=dev @anthropic-ai/claude-code@latest @openai/cod && mkdir -p /paperclip \ && chown node:node /paperclip -COPY docker-entrypoint.sh /usr/local/bin/ +COPY scripts/docker-entrypoint.sh /usr/local/bin/ RUN chmod +x /usr/local/bin/docker-entrypoint.sh ENV NODE_ENV=production \ @@ -65,7 +72,8 @@ ENV NODE_ENV=production \ USER_GID=${USER_GID} \ PAPERCLIP_CONFIG=/paperclip/instances/default/config.json \ PAPERCLIP_DEPLOYMENT_MODE=authenticated \ - PAPERCLIP_DEPLOYMENT_EXPOSURE=private + PAPERCLIP_DEPLOYMENT_EXPOSURE=private \ + OPENCODE_ALLOW_ALL_MODELS=true VOLUME ["/paperclip"] EXPOSE 3100 diff --git a/doc/DEVELOPING.md b/doc/DEVELOPING.md index 1516c84aea..dd9efee637 100644 --- a/doc/DEVELOPING.md +++ b/doc/DEVELOPING.md @@ -97,7 +97,7 @@ docker run --name paperclip \ Or use Compose: ```sh -docker compose -f docker-compose.quickstart.yml up --build +docker compose -f docker/docker-compose.quickstart.yml up --build ``` See `doc/DOCKER.md` for API key wiring (`OPENAI_API_KEY` / `ANTHROPIC_API_KEY`) and persistence details. diff --git a/doc/DOCKER.md b/doc/DOCKER.md index a7055e203c..056a7bdf04 100644 --- a/doc/DOCKER.md +++ b/doc/DOCKER.md @@ -2,6 +2,28 @@ Run Paperclip in Docker without installing Node or pnpm locally. +All commands below assume you are in the **project root** (the directory containing `package.json`), not inside `docker/`. + +## Building the image + +```sh +docker build -t paperclip-local . +``` + +The Dockerfile installs common agent tools (`git`, `gh`, `curl`, `wget`, `ripgrep`, `python3`) and the Claude, Codex, and OpenCode CLIs. + +Build arguments: + +| Arg | Default | Purpose | +|-----|---------|---------| +| `USER_UID` | `1000` | UID for the container `node` user (match your host UID to avoid permission issues on bind mounts) | +| `USER_GID` | `1000` | GID for the container `node` group | + +```sh +docker build -t paperclip-local \ + --build-arg USER_UID=$(id -u) --build-arg USER_GID=$(id -g) . +``` + ## One-liner (build + run) ```sh @@ -10,6 +32,7 @@ docker run --name paperclip \ -p 3100:3100 \ -e HOST=0.0.0.0 \ -e PAPERCLIP_HOME=/paperclip \ + -e BETTER_AUTH_SECRET=$(openssl rand -hex 32) \ -v "$(pwd)/data/docker-paperclip:/paperclip" \ paperclip-local ``` @@ -25,10 +48,15 @@ Data persistence: All persisted under your bind mount (`./data/docker-paperclip` in the example above). -## Compose Quickstart +## Docker Compose + +### Quickstart (embedded SQLite) + +Single container, no external database. Data persists via a bind mount. ```sh -docker compose -f docker-compose.quickstart.yml up --build +BETTER_AUTH_SECRET=$(openssl rand -hex 32) \ + docker compose -f docker/docker-compose.quickstart.yml up --build ``` Defaults: @@ -39,11 +67,36 @@ Defaults: Optional overrides: ```sh -PAPERCLIP_PORT=3200 PAPERCLIP_DATA_DIR=./data/pc docker compose -f docker-compose.quickstart.yml up --build +PAPERCLIP_PORT=3200 PAPERCLIP_DATA_DIR=../data/pc \ + docker compose -f docker/docker-compose.quickstart.yml up --build ``` +**Note:** `PAPERCLIP_DATA_DIR` is resolved relative to the compose file (`docker/`), so `../data/pc` maps to `data/pc` in the project root. + If you change host port or use a non-local domain, set `PAPERCLIP_PUBLIC_URL` to the external URL you will use in browser/auth flows. +Pass `OPENAI_API_KEY` and/or `ANTHROPIC_API_KEY` to enable local adapter runs. + +### Full stack (with PostgreSQL) + +Paperclip server + PostgreSQL 17. The database is health-checked before the server starts. + +```sh +BETTER_AUTH_SECRET=$(openssl rand -hex 32) \ + docker compose -f docker/docker-compose.yml up --build +``` + +PostgreSQL data persists in a named Docker volume (`pgdata`). Paperclip data persists in `paperclip-data`. + +### Untrusted PR review + +Isolated container for reviewing untrusted pull requests with Codex or Claude, without exposing your host machine. See `doc/UNTRUSTED-PR-REVIEW.md` for the full workflow. + +```sh +docker compose -f docker/docker-compose.untrusted-review.yml build +docker compose -f docker/docker-compose.untrusted-review.yml run --rm --service-ports review +``` + ## Authenticated Compose (Single Public URL) For authenticated deployments, set one canonical public URL and let Paperclip derive auth/callback defaults: @@ -93,11 +146,71 @@ Notes: - Without API keys, the app still runs normally. - Adapter environment checks in Paperclip will surface missing auth/CLI prerequisites. -## Untrusted PR Review Container +## Podman Quadlet (systemd) -If you want a separate Docker environment for reviewing untrusted pull requests with `codex` or `claude`, use the dedicated review workflow in `doc/UNTRUSTED-PR-REVIEW.md`. +The `docker/quadlet/` directory contains unit files to run Paperclip + PostgreSQL as systemd services via Podman Quadlet. -That setup keeps CLI auth state in Docker volumes instead of your host home directory and uses a separate scratch workspace for PR checkouts and preview runs. +| File | Purpose | +|------|---------| +| `docker/quadlet/paperclip.pod` | Pod definition — groups containers into a shared network namespace | +| `docker/quadlet/paperclip.container` | Paperclip server — joins the pod, connects to Postgres at `127.0.0.1` | +| `docker/quadlet/paperclip-db.container` | PostgreSQL 17 — joins the pod, health-checked | + +### Setup + +1. Build the image (see above). + +2. Copy quadlet files to your systemd directory: + + ```sh + # Rootless (recommended) + cp docker/quadlet/*.pod docker/quadlet/*.container \ + ~/.config/containers/systemd/ + + # Or rootful + sudo cp docker/quadlet/*.pod docker/quadlet/*.container \ + /etc/containers/systemd/ + ``` + +3. Create a secrets env file (keep out of version control): + + ```sh + cat > ~/.config/containers/systemd/paperclip.env </dev/null 2>&1; then + RUNTIME=docker +elif command -v podman >/dev/null 2>&1; then + RUNTIME=podman +else + echo "SKIP: neither docker nor podman found — skipping build test" + exit 0 +fi + +# Verify the daemon is reachable (docker may be installed but not running) +if ! "$RUNTIME" info >/dev/null 2>&1; then + echo "SKIP: $RUNTIME is installed but not running — skipping build test" + exit 0 +fi + +IMAGE_TAG="paperclip-build-test:$$" +trap '"$RUNTIME" rmi "$IMAGE_TAG" >/dev/null 2>&1 || true' EXIT + +echo "==> Testing Docker build with $RUNTIME" +"$RUNTIME" build \ + -f "$REPO_ROOT/Dockerfile" \ + -t "$IMAGE_TAG" \ + --target production \ + "$REPO_ROOT" + +echo "==> Verifying key binaries in image" +"$RUNTIME" run --rm "$IMAGE_TAG" sh -c ' + set -e + node --version + git --version + gh --version + rg --version + python3 --version + curl --version | head -1 + claude --version 2>/dev/null || echo "claude CLI not found (OK in minimal builds)" +' + +echo "PASS: Docker build test succeeded" diff --git a/docker-entrypoint.sh b/scripts/docker-entrypoint.sh similarity index 100% rename from docker-entrypoint.sh rename to scripts/docker-entrypoint.sh diff --git a/scripts/docker-onboard-smoke.sh b/scripts/docker-onboard-smoke.sh index 97f6743f22..c2830ff0f5 100755 --- a/scripts/docker-onboard-smoke.sh +++ b/scripts/docker-onboard-smoke.sh @@ -242,7 +242,7 @@ echo "==> Building onboard smoke image" docker build \ --build-arg PAPERCLIPAI_VERSION="$PAPERCLIPAI_VERSION" \ --build-arg HOST_UID="$HOST_UID" \ - -f "$REPO_ROOT/Dockerfile.onboard-smoke" \ + -f "$REPO_ROOT/docker/Dockerfile.onboard-smoke" \ -t "$IMAGE_NAME" \ "$REPO_ROOT"