mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-13 03:38:38 +02:00
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The Docker image keeps all instance state under `PAPERCLIP_HOME`,
and deployments mount a volume there
> - Before it drops privileges, the entrypoint makes sure that the
`node` user owns the home (#10530)
> - The ownership probe is a first-mismatch `find` over the whole tree.
When ownership is correct, `-quit` never fires, so every boot stats
every file on the volume
> - On homes with many agent worktrees and `node_modules` trees, this
walk takes minutes before Node starts. That is close to common 300s
healthcheck windows
> - This pull request keeps a cheap shallow probe on every boot. It runs
the full-tree probe only once per volume and node UID/GID, and records
the result in a marker file
> - The benefit is that routine boots no longer scale with the file
count, and the original root-owned-volume fix stays in place
## Linked Issues or Issue Description
Refs #10431 (the open follow-up about the remaining cost of the
ownership walk). Related: #10530 (the change that added the full-tree
probe).
**What happened?**
On a hosted multi-instance deployment, we measured the time from
container start to the first app log line. Instances with many files on
the volume (agent worktrees with `node_modules`) took about 190-235s.
Small instances took 5-22s. One 30 GB volume with few files took 24s, so
the cost follows the file count, not the volume size. Almost all of this
time is the `find "$home_dir" \( ! -user node -o ! -group node \) -print
-quit` walk in `scripts/docker-entrypoint.sh`. When ownership is already
correct, `-quit` never fires, so the walk stats every inode on every
boot.
**Expected behavior**
When ownership is already correct, the entrypoint pre-start phase is
short and does not depend on the number of files in `PAPERCLIP_HOME`. A
fresh root-owned volume, a UID/GID remap, and an upgrade from an older
image are still repaired before the privilege drop.
**Steps to reproduce**
1. Run the image with a volume on `/paperclip` that holds a few hundred
thousand files, all owned by `node:node`.
2. Restart the container.
3. Compare the container start time with the first server log line. The
gap grows with the file count, even though no `chown` runs.
**Paperclip version or commit**
`master` at a9830414c7
**Deployment mode**
Docker production image (any platform that mounts a volume on
`PAPERCLIP_HOME`)
## What Changed
- `scripts/docker-entrypoint.sh`: the ownership probe is now tiered.
- Every boot runs a shallow probe (`find -maxdepth 3`) over the home and
its first three levels. A fresh root-owned mount and a UID/GID remap
both change the owner of the home directory, so this probe catches them.
- The full-tree probe runs only when
`PAPERCLIP_HOME/.paperclip-ownership-verified` is missing or records a
different node UID/GID (format `v1 <uid>:<gid>`). This catches deep
mismatches, including the first boot after an upgrade from an older
image.
- Any mismatch repairs the whole tree with `chown -R node:node`, as
before. After a clean probe or a repair, the entrypoint writes the
marker as the `node` user (through `gosu`), so the marker never trips
the shallow probe. A failed marker write is ignored: the only cost is a
full probe on the next boot. If `find` exits non-zero without a mismatch
(for example, an I/O error), the walk is incomplete, so the entrypoint
does not write the marker and the next boot probes again.
- `scripts/docker-entrypoint.sh`: timestamped log lines at entrypoint
start, around the full probe and the repair (with durations), and right
before the `exec` of the server. You can now measure the pre-start phase
from the container log.
- `server/src/__tests__/docker-entrypoint.test.ts`: the `find` stub now
tells the shallow probe from the full probe. New tests cover each path
(see Verification).
- `doc/DOCKER.md`: documents the marker, how to force a full check, and
the new log lines.
## Verification
- `cd server && npx vitest run src/__tests__/docker-entrypoint.test.ts
src/__tests__/container-init-reaping.test.ts
src/__tests__/docker-build-stamp.test.ts`: 40 passed. New cases:
- correct ownership + current marker → shallow probe only, no full walk,
no `chown`
- current marker + deep-only mismatch → full walk skipped (this is the
documented trade-off)
- no marker (upgrade) → one full walk, no `chown`, marker written as `v1
1000:1000`
- no marker + deep mismatch → full walk finds it, `chown -R`, marker
written
- root-owned top level with a current marker → `chown -R` without a full
walk
- full walk fails without a mismatch → no `chown`, no marker, retry
logged
- marker for another UID/GID → full walk runs again, marker rewritten as
`v1 1001:1001`
- timestamped `starting`, `ownership repair finished in Ns`, and
`starting app as node` lines, in order, before the command runs
- The new tests fail against the old script. The 11 existing tests pass
unchanged.
- `cd server && npx tsc --noEmit`: passes.
- `shellcheck -s sh scripts/docker-entrypoint.sh`: clean. `dash -n`:
syntax OK.
- Manual run with the real `find` (BSD find, script run under `dash`)
against a 200k-file tree. Only `id`/`usermod`/`groupmod`/`chown`/`gosu`
were stubbed, and `node` was mapped to the local user and group:
- boot 1, no marker, one deep group mismatch → full probe, `chown -R`,
marker written
- boot 2, marker present → no full probe, no `chown`
- boot 3, no marker, clean tree → full probe, no `chown`, marker written
- boot 4, marker present, clean → no full probe, no `chown`
- boot 5, top-level group mismatch with marker → `chown -R`
- I did not build the image or measure boot time in a container. Docker
was not available on the machine I used. On a warm local disk the full
walk is sub-second, so the local timings say nothing about cold network
block storage.
## Risks
- Once the marker is current, a mismatch deeper than three levels is not
detected automatically. Examples: files written as root by `docker exec`
or a restore run as root, after the first verified boot. Before this
change, those files were repaired on the next boot. The workaround is in
`doc/DOCKER.md`: delete the marker and restart. A remap, a fresh volume,
and a root-owned top level are still detected on every boot.
- On the first boot after an upgrade, each existing volume pays one full
walk (the same cost as today). Later boots skip it.
- The marker is a new dotfile in `PAPERCLIP_HOME`. Backups and copies
that include the home carry it along. This is safe, because the marker
records the UID/GID that it verified, and a different UID/GID forces a
new full probe.
- The non-root path (restricted PodSecurity, OpenShift) does not change,
apart from two new stdout log lines.
## Model Used
- Claude Opus 5.5 (`claude-opus-5-5`, 1M context) via Claude Code, with
tool use (shell, file editing). Default reasoning mode.
## 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 with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
394 lines
18 KiB
Markdown
394 lines
18 KiB
Markdown
# Docker Quickstart
|
|
|
|
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 |
|
|
| `CLI_TOOLS_CACHE_EPOCH` | empty | Refresh the CLI-install layer; CI supplies the current ISO week |
|
|
| `PAPERCLIP_BUILD_VERSION` | empty | Runtime version when Git metadata is unavailable |
|
|
| `PAPERCLIP_BUILD_COMMIT` | empty | Source commit written into the server build stamp and runtime environment |
|
|
|
|
Changing the build version or commit preserves the CLI-install cache. The
|
|
tool layer refreshes when its weekly epoch, base image, installation command,
|
|
or earlier build inputs change. Local builds can set a new epoch explicitly
|
|
to refresh tools without clearing the entire build cache.
|
|
|
|
```sh
|
|
docker build -t paperclip-local \
|
|
--build-arg USER_UID=$(id -u) --build-arg USER_GID=$(id -g) .
|
|
```
|
|
|
|
## Standard images and downstream composition
|
|
|
|
The Docker workflow publishes the standard `production` target for Linux AMD64
|
|
and ARM64. Canonical master pushes also publish
|
|
`ghcr.io/paperclipai/paperclip:sha-<FULL_SHA>` and a GitHub/Sigstore attestation
|
|
for its immutable multi-platform digest. Downstream services can compose their
|
|
own images from this public base without rebuilding Core.
|
|
|
|
Stamped standard images include the build-owned remote provider pack at
|
|
`/opt/paperclip-runner/provider-pack` and set
|
|
`PAPERCLIP_RUNNER_REMOTE_PROVIDER_PACK_PATH` to that directory. Downstream
|
|
compositions and the explicit `cloud` target inherit both. The pack is required
|
|
on the controller for remote OpenCode and ACPX runs, even when the sandbox has
|
|
preinstalled providers; setting the environment variable alone cannot repair an
|
|
image that omits the pack. Rebuild and redeploy from a standard image containing
|
|
the pack if runs fail with `runner_remote_provider_artifact_incompatible`.
|
|
|
|
Local Docker builds must supply a full `PAPERCLIP_BUILD_COMMIT` to include a
|
|
qualified pack. Unstamped builds remain usable for local adapters and skip pack
|
|
generation. The pack stays root-owned and readable by remapped runtime UIDs.
|
|
|
|
The legacy recurring public `-cloud` publisher is retired. Master pushes,
|
|
release tags, and manual `Docker` dispatches no longer build that variant.
|
|
Existing `-cloud` tags and digests remain in the registry for rollback; their
|
|
release-channel aliases no longer advance. This change deletes no images,
|
|
cache tags, or migrator artifacts.
|
|
|
|
The `cloud` Dockerfile target remains available for explicit
|
|
[preview builds](preview-release-artifacts.md). Those requests still publish a
|
|
full-SHA `-cloud` tag when needed. They do not advance a release channel or
|
|
replace downstream private composition.
|
|
|
|
A published image alone does not prove source tests or migration compatibility.
|
|
Downstream deployment tooling must verify [source proof](cloud-build-readiness.md),
|
|
the standard image attestation, the exact-source migrator, and its own composed
|
|
image before rollout. Resolve immutable digests instead of deploying mutable tags.
|
|
|
|
## One-liner (build + run)
|
|
|
|
```sh
|
|
docker build -t paperclip-local . && \
|
|
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) \
|
|
-e PAPERCLIP_TOOL_ACTION_SIGNING_SECRET=$(openssl rand -hex 32) \
|
|
-v "$(pwd)/data/docker-paperclip:/paperclip" \
|
|
paperclip-local
|
|
```
|
|
|
|
Open: `http://localhost:3100`
|
|
|
|
Data persistence:
|
|
|
|
- Embedded PostgreSQL data
|
|
- uploaded assets
|
|
- local secrets key
|
|
- local agent workspace data
|
|
|
|
All persisted under your bind mount (`./data/docker-paperclip` in the example above).
|
|
|
|
## Docker Compose
|
|
|
|
### Quickstart (embedded SQLite)
|
|
|
|
Single container, no external database. Data persists via a bind mount.
|
|
|
|
```sh
|
|
BETTER_AUTH_SECRET=$(openssl rand -hex 32) \
|
|
PAPERCLIP_TOOL_ACTION_SIGNING_SECRET=$(openssl rand -hex 32) \
|
|
docker compose -f docker/docker-compose.quickstart.yml up --build
|
|
```
|
|
|
|
Defaults:
|
|
|
|
- host port: `3100`
|
|
- persistent data dir: `./data/docker-paperclip`
|
|
|
|
Optional overrides:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```yaml
|
|
services:
|
|
paperclip:
|
|
environment:
|
|
PAPERCLIP_DEPLOYMENT_MODE: authenticated
|
|
PAPERCLIP_DEPLOYMENT_EXPOSURE: private
|
|
PAPERCLIP_PUBLIC_URL: https://desk.koker.net
|
|
```
|
|
|
|
`PAPERCLIP_PUBLIC_URL` is used as the primary source for:
|
|
|
|
- auth public base URL
|
|
- Better Auth base URL defaults
|
|
- bootstrap invite URL defaults
|
|
- hostname allowlist defaults (hostname extracted from URL)
|
|
|
|
For fresh `authenticated/private` Docker or appliance-style installs, the first
|
|
admin can now be claimed entirely from the browser after sign-in. Open the
|
|
Paperclip URL, sign in or create an account, then choose `Claim this instance`
|
|
on the setup screen. This browser claim is disabled for `authenticated/public`;
|
|
public deployments should run the high-entropy CLI invite fallback instead:
|
|
|
|
```sh
|
|
pnpm paperclipai auth bootstrap-ceo
|
|
```
|
|
|
|
Granular overrides remain available if needed (`PAPERCLIP_AUTH_PUBLIC_BASE_URL`, `BETTER_AUTH_URL`, `BETTER_AUTH_TRUSTED_ORIGINS`, `PAPERCLIP_ALLOWED_HOSTNAMES`).
|
|
|
|
Set `PAPERCLIP_ALLOWED_HOSTNAMES` explicitly only when you need additional hostnames beyond the public URL host (for example Tailscale/LAN aliases or multiple private hostnames).
|
|
|
|
### Optional Vercel Connect credentials
|
|
|
|
Vercel Connect's backend integration is retained for controlled testing and
|
|
existing Vercel-backed connections, but its new-connection UI is currently
|
|
withheld from **Apps → Browse**. Setting
|
|
`PAPERCLIP_VERCEL_CONNECT_ENABLED=true` does not expose a customer-facing setup
|
|
entry. Native provider setup screens remain unchanged. Vercel-hosted deployments use the
|
|
workload OIDC token Vercel injects. Other hosted and self-hosted deployments
|
|
can provide `PAPERCLIP_VERCEL_CONNECT_ACCESS_TOKEN` as a deployment bootstrap
|
|
secret only when that token type is accepted by the live Connect API:
|
|
|
|
```yaml
|
|
services:
|
|
paperclip:
|
|
environment:
|
|
PAPERCLIP_VERCEL_CONNECT_ENABLED: "true"
|
|
PAPERCLIP_VERCEL_CONNECT_ACCESS_TOKEN: ${PAPERCLIP_VERCEL_CONNECT_ACCESS_TOKEN}
|
|
```
|
|
|
|
Do not save that access token in a company secret or connection config. It is
|
|
instance bootstrap authority for the operator-selected Vercel account. A token's
|
|
long expiry and broad Vercel scope do not prove Connect compatibility; validate
|
|
it with connector metadata before rollout. Workload OIDC takes precedence when
|
|
both authorities are present. Turning the feature flag off hides new
|
|
Vercel-backed setup; existing connections keep resolving while workload OIDC or
|
|
the bootstrap token remains available. Missing or invalid authority fails
|
|
closed. See the [Vercel Connect operator guide](./connections/VERCEL-CONNECT.md).
|
|
|
|
## Claude + Codex Local Adapters in Docker
|
|
|
|
The image pre-installs:
|
|
|
|
- `claude` (Anthropic Claude Code CLI)
|
|
- `codex` (OpenAI Codex CLI)
|
|
- `python3` (used to run local subscription sign-in in a pseudo-terminal)
|
|
|
|
Start the container, then open **Apps → Connections** to connect a Claude or
|
|
Codex subscription in your browser. You can also enter a provider API key there.
|
|
You do not run a shell command or configure a CLI home.
|
|
|
|
```sh
|
|
docker run --name paperclip \
|
|
-p 3100:3100 \
|
|
-e HOST=0.0.0.0 \
|
|
-e PAPERCLIP_HOME=/paperclip \
|
|
-v "$(pwd)/data/docker-paperclip:/paperclip" \
|
|
paperclip-local
|
|
```
|
|
|
|
Notes:
|
|
|
|
- The image provides the CLI and Python prerequisites for local subscription sign-in.
|
|
- Adapter environment checks in Paperclip surface missing authentication or CLI prerequisites.
|
|
|
|
## Podman Quadlet (systemd)
|
|
|
|
The `docker/quadlet/` directory contains unit files to run Paperclip + PostgreSQL as systemd services via Podman Quadlet.
|
|
|
|
| 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)
|
|
PAPERCLIP_TOOL_ACTION_SIGNING_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)
|
|
|
|
Use this when you want to mimic a fresh machine that only has Ubuntu + npm and verify:
|
|
|
|
- `npx paperclipai onboard --yes` completes
|
|
- the server binds to `0.0.0.0:3100` so host access works
|
|
- onboard/run banners and startup logs are visible in your terminal
|
|
|
|
Build + run:
|
|
|
|
```sh
|
|
./scripts/docker-onboard-smoke.sh
|
|
```
|
|
|
|
Open: `http://localhost:3131` (default smoke host port)
|
|
|
|
Useful overrides:
|
|
|
|
```sh
|
|
HOST_PORT=3200 PAPERCLIPAI_VERSION=latest ./scripts/docker-onboard-smoke.sh
|
|
PAPERCLIP_DEPLOYMENT_MODE=authenticated PAPERCLIP_DEPLOYMENT_EXPOSURE=private ./scripts/docker-onboard-smoke.sh
|
|
SMOKE_DETACH=true SMOKE_METADATA_FILE=/tmp/paperclip-smoke.env PAPERCLIPAI_VERSION=latest ./scripts/docker-onboard-smoke.sh
|
|
```
|
|
|
|
Notes:
|
|
|
|
- Persistent data is mounted at `./data/docker-onboard-smoke` by default.
|
|
- Container runtime user id defaults to your local `id -u` so the mounted data dir stays writable while avoiding root runtime.
|
|
- Smoke script defaults to `authenticated/private` mode so `HOST=0.0.0.0` can be exposed to the host.
|
|
- Smoke script defaults host port to `3131` to avoid conflicts with local Paperclip on `3100`.
|
|
- Smoke script also defaults `PAPERCLIP_PUBLIC_URL` to `http://localhost:<HOST_PORT>` so bootstrap invite URLs and auth callbacks use the reachable host port instead of the container's internal `3100`.
|
|
- 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.
|
|
- Set `SMOKE_DETACH=true` to leave the container running for automation and optionally write shell-ready metadata to `SMOKE_METADATA_FILE`.
|
|
- Set `SMOKE_CONTAINER_NAME` to fix the container's name up front. Automation that has to collect diagnostics when the script *fails* needs a name it already knows, rather than one it can only read back out of a successful run. Defaults to the image name.
|
|
- The container's logs are dumped to `SMOKE_LOG_FILE` (default `$TMPDIR/<container name>.log`) before the script tears the container down, so a run that never became ready still leaves its logs behind.
|
|
- 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.
|
|
- Before it drops privileges, the entrypoint makes sure `PAPERCLIP_HOME` is owned by the `node` user. Each boot checks only the home directory and its first three levels. The first boot (and any boot after a UID/GID change) also checks the full tree, and then writes `PAPERCLIP_HOME/.paperclip-ownership-verified`. While that marker is current, later boots skip the full-tree walk, which can take minutes on homes with many files. A mismatch triggers a recursive `chown`. If files deep in the tree get the wrong owner (for example, after a restore as root), delete the marker and restart to force a full check. If a check fails (for example, with an I/O error), the entrypoint does not write the marker, so the next boot checks again.
|
|
- The entrypoint writes timestamped lines when it starts, around the full ownership check or any repair (with the duration), and just before it starts the server, so you can measure the pre-start time from the container log.
|
|
- Paperclip data persists via Docker volumes/bind mounts (compose) or at `~/.local/share/paperclip` (quadlet).
|
|
|
|
## Native Runner build cache
|
|
|
|
The image compiles the native Runner in `runner-build`, before copying the
|
|
application source. A pinned `cargo-chef` generates a dependency recipe in
|
|
`runner-plan`. The separate `runner-deps` stage compiles that recipe with the
|
|
package-owned Rust compiler. Both the dependency build and the real binary use
|
|
the release profile and locked Cargo dependencies. The recipe stage never
|
|
modifies source in the checkout.
|
|
|
|
Changes to Rust source or embedded protocol inputs rebuild the real binary but
|
|
can reuse compiled dependencies when the recipe is unchanged. Dependency
|
|
manifests, the Cargo lockfile, target metadata, or compiler changes invalidate
|
|
the relevant cache. Ordinary server or UI changes can reuse the entire native
|
|
build through the existing registry cache (`mode=max`). Each platform gets its
|
|
own native build; no cross-architecture binary is reused. No additional GitHub
|
|
Actions cache is created. A cold build also installs the recipe generator and
|
|
compiles dependencies, so the savings apply after those layers are available.
|
|
|
|
Cloud builds import one registry cache: the first available full-SHA cache in
|
|
the current commit's ten-entry first-parent ancestry, with the legacy cache
|
|
as a final fallback. Each build still exports its own SHA cache with
|
|
`mode=max`. In fresh-builder checks, importing several historical manifests
|
|
missed native layers that a single matching manifest reused. The selector
|
|
inspects metadata after Docker login, stops at the first available cache, and
|
|
permits a cold build if no cache can be read.
|
|
|
|
The application build inherits that stage and still runs the normal server
|
|
build, including Cargo, binary staging, and generated-contract checks. Rust
|
|
input file times are normalized in both stages so fresh checkouts do not force
|
|
Cargo to rebuild unchanged source. Changes made by build scripts still reach
|
|
Cargo's normal validation. The final application copy excludes Cargo's target
|
|
directory as before. Cache misses only cost compilation time.
|
|
|
|
Pull requests that change the Dockerfile, Docker ignore rules, or Runner native
|
|
inputs also build the isolated `runner-build` target in `Docker Runner check`.
|
|
The check runs `bash scripts/check-docker-runner-cache.sh` against a disposable
|
|
copy of tracked source and the actual Docker ignore rules. It compiles a baseline
|
|
and exports a local cache, removes that builder, changes a Rust metadata constant,
|
|
and rebuilds on a fresh builder using only the exported cache. It requires a
|
|
cached dependency build, an unchanged dependency recipe, and changed metadata
|
|
from the real binary. It also verifies that a dependency declaration change
|
|
alters the recipe. The probe exports small metadata results instead of importing
|
|
a large test image into the Docker daemon. Temporary builders and cache files
|
|
are removed afterward. It catches missing embedded inputs before the post-merge
|
|
build. It uses a GitHub-hosted runner with read-only repository access and never
|
|
publishes images or registry caches. Allow up to 20 minutes for its cold build and
|
|
source rebuild.
|