mirror of
https://github.com/paperclipai/paperclip
synced 2026-04-25 17:25:15 +02:00
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 <noreply@paperclip.ing>
This commit is contained in:
20
Dockerfile
20
Dockerfile
@@ -2,10 +2,17 @@ FROM node:lts-trixie-slim AS base
|
|||||||
ARG USER_UID=1000
|
ARG USER_UID=1000
|
||||||
ARG USER_GID=1000
|
ARG USER_GID=1000
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install -y --no-install-recommends ca-certificates curl git gosu \
|
&& apt-get install -y --no-install-recommends ca-certificates gosu curl git wget ripgrep python3 \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& 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 \
|
||||||
RUN corepack enable
|
&& 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
|
# Modify the existing node user/group to have the specified UID/GID to match host user
|
||||||
RUN usermod -u $USER_UID --non-unique node \
|
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 \
|
&& mkdir -p /paperclip \
|
||||||
&& chown node:node /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
|
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||||
|
|
||||||
ENV NODE_ENV=production \
|
ENV NODE_ENV=production \
|
||||||
@@ -65,7 +72,8 @@ ENV NODE_ENV=production \
|
|||||||
USER_GID=${USER_GID} \
|
USER_GID=${USER_GID} \
|
||||||
PAPERCLIP_CONFIG=/paperclip/instances/default/config.json \
|
PAPERCLIP_CONFIG=/paperclip/instances/default/config.json \
|
||||||
PAPERCLIP_DEPLOYMENT_MODE=authenticated \
|
PAPERCLIP_DEPLOYMENT_MODE=authenticated \
|
||||||
PAPERCLIP_DEPLOYMENT_EXPOSURE=private
|
PAPERCLIP_DEPLOYMENT_EXPOSURE=private \
|
||||||
|
OPENCODE_ALLOW_ALL_MODELS=true
|
||||||
|
|
||||||
VOLUME ["/paperclip"]
|
VOLUME ["/paperclip"]
|
||||||
EXPOSE 3100
|
EXPOSE 3100
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ docker run --name paperclip \
|
|||||||
Or use Compose:
|
Or use Compose:
|
||||||
|
|
||||||
```sh
|
```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.
|
See `doc/DOCKER.md` for API key wiring (`OPENAI_API_KEY` / `ANTHROPIC_API_KEY`) and persistence details.
|
||||||
|
|||||||
132
doc/DOCKER.md
132
doc/DOCKER.md
@@ -2,6 +2,28 @@
|
|||||||
|
|
||||||
Run Paperclip in Docker without installing Node or pnpm locally.
|
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)
|
## One-liner (build + run)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -10,6 +32,7 @@ docker run --name paperclip \
|
|||||||
-p 3100:3100 \
|
-p 3100:3100 \
|
||||||
-e HOST=0.0.0.0 \
|
-e HOST=0.0.0.0 \
|
||||||
-e PAPERCLIP_HOME=/paperclip \
|
-e PAPERCLIP_HOME=/paperclip \
|
||||||
|
-e BETTER_AUTH_SECRET=$(openssl rand -hex 32) \
|
||||||
-v "$(pwd)/data/docker-paperclip:/paperclip" \
|
-v "$(pwd)/data/docker-paperclip:/paperclip" \
|
||||||
paperclip-local
|
paperclip-local
|
||||||
```
|
```
|
||||||
@@ -25,10 +48,15 @@ Data persistence:
|
|||||||
|
|
||||||
All persisted under your bind mount (`./data/docker-paperclip` in the example above).
|
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
|
```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:
|
Defaults:
|
||||||
@@ -39,11 +67,36 @@ Defaults:
|
|||||||
Optional overrides:
|
Optional overrides:
|
||||||
|
|
||||||
```sh
|
```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.
|
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)
|
## Authenticated Compose (Single Public URL)
|
||||||
|
|
||||||
For authenticated deployments, set one canonical public URL and let Paperclip derive auth/callback defaults:
|
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.
|
- Without API keys, the app still runs normally.
|
||||||
- Adapter environment checks in Paperclip will surface missing auth/CLI prerequisites.
|
- 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 <<EOL
|
||||||
|
BETTER_AUTH_SECRET=$(openssl rand -hex 32)
|
||||||
|
POSTGRES_USER=paperclip
|
||||||
|
POSTGRES_PASSWORD=paperclip
|
||||||
|
POSTGRES_DB=paperclip
|
||||||
|
DATABASE_URL=postgres://paperclip:paperclip@127.0.0.1:5432/paperclip
|
||||||
|
# OPENAI_API_KEY=sk-...
|
||||||
|
# ANTHROPIC_API_KEY=sk-...
|
||||||
|
EOL
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Create the data directory and start:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mkdir -p ~/.local/share/paperclip
|
||||||
|
systemctl --user daemon-reload
|
||||||
|
systemctl --user start paperclip-pod
|
||||||
|
```
|
||||||
|
|
||||||
|
### Quadlet management
|
||||||
|
|
||||||
|
```sh
|
||||||
|
journalctl --user -u paperclip -f # App logs
|
||||||
|
journalctl --user -u paperclip-db -f # DB logs
|
||||||
|
systemctl --user status paperclip-pod # Pod status
|
||||||
|
systemctl --user restart paperclip-pod # Restart all
|
||||||
|
systemctl --user stop paperclip-pod # Stop all
|
||||||
|
```
|
||||||
|
|
||||||
|
### Quadlet notes
|
||||||
|
|
||||||
|
- **First boot**: Unlike Docker Compose's `condition: service_healthy`, Quadlet's `After=` only waits for the DB unit to *start*, not for PostgreSQL to be ready. On a cold first boot you may see one or two restart attempts in `journalctl --user -u paperclip` while PostgreSQL initialises — this is expected and resolves automatically via `Restart=on-failure`.
|
||||||
|
- Containers in a pod share `localhost`, so Paperclip reaches Postgres at `127.0.0.1:5432`.
|
||||||
|
- PostgreSQL data persists in the `paperclip-pgdata` named volume.
|
||||||
|
- Paperclip data persists at `~/.local/share/paperclip`.
|
||||||
|
- For rootful quadlet deployment, remove `%h` prefixes and use absolute paths.
|
||||||
|
|
||||||
## Onboard Smoke Test (Ubuntu + npm only)
|
## Onboard Smoke Test (Ubuntu + npm only)
|
||||||
|
|
||||||
@@ -133,4 +246,9 @@ Notes:
|
|||||||
- In authenticated mode, the smoke script defaults `SMOKE_AUTO_BOOTSTRAP=true` and drives the real bootstrap path automatically: it signs up a real user, runs `paperclipai auth bootstrap-ceo` inside the container to mint a real bootstrap invite, accepts that invite over HTTP, and verifies board session access.
|
- In authenticated mode, the smoke script defaults `SMOKE_AUTO_BOOTSTRAP=true` and drives the real bootstrap path automatically: it signs up a real user, runs `paperclipai auth bootstrap-ceo` inside the container to mint a real bootstrap invite, accepts that invite over HTTP, and verifies board session access.
|
||||||
- Run the script in the foreground to watch the onboarding flow; stop with `Ctrl+C` after validation.
|
- Run the script in the foreground to watch the onboarding flow; stop with `Ctrl+C` after validation.
|
||||||
- Set `SMOKE_DETACH=true` to leave the container running for automation and optionally write shell-ready metadata to `SMOKE_METADATA_FILE`.
|
- Set `SMOKE_DETACH=true` to leave the container running for automation and optionally write shell-ready metadata to `SMOKE_METADATA_FILE`.
|
||||||
- The image definition is in `Dockerfile.onboard-smoke`.
|
- The image definition is in `docker/Dockerfile.onboard-smoke`.
|
||||||
|
|
||||||
|
## General Notes
|
||||||
|
|
||||||
|
- The `docker-entrypoint.sh` adjusts the container `node` user UID/GID at startup to match the values passed via `USER_UID`/`USER_GID`, avoiding permission issues on bind-mounted volumes.
|
||||||
|
- Paperclip data persists via Docker volumes/bind mounts (compose) or at `~/.local/share/paperclip` (quadlet).
|
||||||
|
|||||||
@@ -16,14 +16,14 @@ By default this workflow does **not** mount your host repo checkout, your host h
|
|||||||
## Files
|
## Files
|
||||||
|
|
||||||
- `docker/untrusted-review/Dockerfile`
|
- `docker/untrusted-review/Dockerfile`
|
||||||
- `docker-compose.untrusted-review.yml`
|
- `docker/docker-compose.untrusted-review.yml`
|
||||||
- `review-checkout-pr` inside the container
|
- `review-checkout-pr` inside the container
|
||||||
|
|
||||||
## Build and start a shell
|
## Build and start a shell
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
docker compose -f docker-compose.untrusted-review.yml build
|
docker compose -f docker/docker-compose.untrusted-review.yml build
|
||||||
docker compose -f docker-compose.untrusted-review.yml run --rm --service-ports review
|
docker compose -f docker/docker-compose.untrusted-review.yml run --rm --service-ports review
|
||||||
```
|
```
|
||||||
|
|
||||||
That opens an interactive shell in the review container with:
|
That opens an interactive shell in the review container with:
|
||||||
@@ -47,7 +47,7 @@ claude login
|
|||||||
If you prefer API-key auth instead of CLI login, pass keys through Compose env:
|
If you prefer API-key auth instead of CLI login, pass keys through Compose env:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
OPENAI_API_KEY=... ANTHROPIC_API_KEY=... docker compose -f docker-compose.untrusted-review.yml run --rm review
|
OPENAI_API_KEY=... ANTHROPIC_API_KEY=... docker compose -f docker/docker-compose.untrusted-review.yml run --rm review
|
||||||
```
|
```
|
||||||
|
|
||||||
## Check out a PR safely
|
## Check out a PR safely
|
||||||
@@ -117,7 +117,7 @@ Notes:
|
|||||||
Remove the review container volumes when you want a clean environment:
|
Remove the review container volumes when you want a clean environment:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
docker compose -f docker-compose.untrusted-review.yml down -v
|
docker compose -f docker/docker-compose.untrusted-review.yml down -v
|
||||||
```
|
```
|
||||||
|
|
||||||
That deletes:
|
That deletes:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
services:
|
services:
|
||||||
paperclip:
|
paperclip:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: ..
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
ports:
|
ports:
|
||||||
- "${PAPERCLIP_PORT:-3100}:3100"
|
- "${PAPERCLIP_PORT:-3100}:3100"
|
||||||
@@ -15,4 +15,4 @@ services:
|
|||||||
PAPERCLIP_PUBLIC_URL: "${PAPERCLIP_PUBLIC_URL:-http://localhost:3100}"
|
PAPERCLIP_PUBLIC_URL: "${PAPERCLIP_PUBLIC_URL:-http://localhost:3100}"
|
||||||
BETTER_AUTH_SECRET: "${BETTER_AUTH_SECRET:?BETTER_AUTH_SECRET must be set}"
|
BETTER_AUTH_SECRET: "${BETTER_AUTH_SECRET:?BETTER_AUTH_SECRET must be set}"
|
||||||
volumes:
|
volumes:
|
||||||
- "${PAPERCLIP_DATA_DIR:-./data/docker-paperclip}:/paperclip"
|
- "${PAPERCLIP_DATA_DIR:-../data/docker-paperclip}:/paperclip"
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
services:
|
services:
|
||||||
review:
|
review:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: ..
|
||||||
dockerfile: docker/untrusted-review/Dockerfile
|
dockerfile: docker/untrusted-review/Dockerfile
|
||||||
init: true
|
init: true
|
||||||
tty: true
|
tty: true
|
||||||
@@ -16,7 +16,9 @@ services:
|
|||||||
- pgdata:/var/lib/postgresql/data
|
- pgdata:/var/lib/postgresql/data
|
||||||
|
|
||||||
server:
|
server:
|
||||||
build: .
|
build:
|
||||||
|
context: ..
|
||||||
|
dockerfile: Dockerfile
|
||||||
ports:
|
ports:
|
||||||
- "3100:3100"
|
- "3100:3100"
|
||||||
environment:
|
environment:
|
||||||
20
docker/quadlet/paperclip-db.container
Normal file
20
docker/quadlet/paperclip-db.container
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=PostgreSQL for Paperclip
|
||||||
|
|
||||||
|
[Container]
|
||||||
|
Image=docker.io/library/postgres:17-alpine
|
||||||
|
ContainerName=paperclip-db
|
||||||
|
Pod=paperclip.pod
|
||||||
|
Volume=paperclip-pgdata:/var/lib/postgresql/data
|
||||||
|
EnvironmentFile=%h/.config/containers/systemd/paperclip.env
|
||||||
|
HealthCmd=pg_isready -U $POSTGRES_USER -d $POSTGRES_DB -h localhost || exit 1
|
||||||
|
HealthInterval=15s
|
||||||
|
HealthTimeout=5s
|
||||||
|
HealthRetries=5
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Restart=on-failure
|
||||||
|
TimeoutStartSec=60
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
23
docker/quadlet/paperclip.container
Normal file
23
docker/quadlet/paperclip.container
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Paperclip AI Agent Orchestrator
|
||||||
|
Requires=paperclip-db.service
|
||||||
|
After=paperclip-db.service
|
||||||
|
|
||||||
|
[Container]
|
||||||
|
Image=paperclip-local
|
||||||
|
ContainerName=paperclip
|
||||||
|
Pod=paperclip.pod
|
||||||
|
Volume=%h/.local/share/paperclip:/paperclip:Z
|
||||||
|
Environment=HOST=0.0.0.0
|
||||||
|
Environment=PAPERCLIP_HOME=/paperclip
|
||||||
|
Environment=PAPERCLIP_DEPLOYMENT_MODE=authenticated
|
||||||
|
Environment=PAPERCLIP_DEPLOYMENT_EXPOSURE=private
|
||||||
|
Environment=PAPERCLIP_PUBLIC_URL=http://localhost:3100
|
||||||
|
EnvironmentFile=%h/.config/containers/systemd/paperclip.env
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Restart=on-failure
|
||||||
|
TimeoutStartSec=120
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
3
docker/quadlet/paperclip.pod
Normal file
3
docker/quadlet/paperclip.pod
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[Pod]
|
||||||
|
PodName=paperclip
|
||||||
|
PublishPort=3100:3100
|
||||||
@@ -8,7 +8,7 @@ Run Paperclip in Docker without installing Node or pnpm locally.
|
|||||||
## Compose Quickstart (Recommended)
|
## Compose Quickstart (Recommended)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
docker compose -f docker-compose.quickstart.yml up --build
|
docker compose -f docker/docker-compose.quickstart.yml up --build
|
||||||
```
|
```
|
||||||
|
|
||||||
Open [http://localhost:3100](http://localhost:3100).
|
Open [http://localhost:3100](http://localhost:3100).
|
||||||
@@ -21,10 +21,12 @@ Defaults:
|
|||||||
Override with environment variables:
|
Override with environment variables:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
PAPERCLIP_PORT=3200 PAPERCLIP_DATA_DIR=./data/pc \
|
PAPERCLIP_PORT=3200 PAPERCLIP_DATA_DIR=../data/pc \
|
||||||
docker compose -f docker-compose.quickstart.yml up --build
|
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.
|
||||||
|
|
||||||
## Manual Docker Build
|
## Manual Docker Build
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|||||||
46
scripts/docker-build-test.sh
Executable file
46
scripts/docker-build-test.sh
Executable file
@@ -0,0 +1,46 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Verify the Docker image builds successfully.
|
||||||
|
# Skips gracefully when docker/podman is not available.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
|
||||||
|
# Detect container runtime
|
||||||
|
if command -v docker >/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"
|
||||||
@@ -242,7 +242,7 @@ echo "==> Building onboard smoke image"
|
|||||||
docker build \
|
docker build \
|
||||||
--build-arg PAPERCLIPAI_VERSION="$PAPERCLIPAI_VERSION" \
|
--build-arg PAPERCLIPAI_VERSION="$PAPERCLIPAI_VERSION" \
|
||||||
--build-arg HOST_UID="$HOST_UID" \
|
--build-arg HOST_UID="$HOST_UID" \
|
||||||
-f "$REPO_ROOT/Dockerfile.onboard-smoke" \
|
-f "$REPO_ROOT/docker/Dockerfile.onboard-smoke" \
|
||||||
-t "$IMAGE_NAME" \
|
-t "$IMAGE_NAME" \
|
||||||
"$REPO_ROOT"
|
"$REPO_ROOT"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user