Docker¶
paperful is a local CLI. The operator install is git clone and
docker compose build. The image is build-local only (paperful:local).
There is no docker pull and no PyPI package. Zotero and headed browser
login live on the host. Docker does not replace them.
The Compose image is a one-shot pack — Python 3.12, paperful, Poppler
(pdftotext), OCRmyPDF and Tesseract (eng), Playwright Chromium — for unattended commands (run, lint,
ocr, report, attach once a write key exists). It is not a daemon and not a
complete environment. Contributors use uv
(see the README Develop section).
Durable data — config, custom playbook packs, out/, and state/ — still
lives outside the container (and, by default, outside the git root).
What still runs on the host¶
Zotero (GUI, local API, “Always allow”)
paperful session login scholar|ezproxy(headed Chrome/Edge on the host: campus SSO, Scholar CAPTCHA)paperful session login mendeley(Elsevier OAuth; the localhost redirect will not reach a container — see Mendeley)EndNote
.enl/.Data(desktop library on the host; paperful never writes SQLite — see EndNote)Any scripts that read
out//state/as files
The container talks to host Zotero over the local API (:23119). Session
vaults and the write key must be the same state/ tree the container
mounts (PAPERFUL_DATA).
Prerequisites¶
Docker with Compose v2
Zotero running on the host, local API enabled (Settings → Advanced → Allow other applications on this computer to communicate with Zotero)
For attach /
fix-metadata --apply: complete the Zotero “Always allow” dialog once on the host (key is stored understate/)For Scholar / EZProxy sessions: a host
uvinstall so you can runpaperful session login …, then reuse the mountedstate/sessions/from the container. Interactivedoctor(default on a TTY) walks you through this and re-checks.
Quick start¶
git clone https://github.com/glen-w/Paperful.git
cd Paperful
cp .env.example .env
Keep your current repo-local setup (no data move):
cp .env.example .env
# edit .env: PAPERFUL_DATA=.
docker compose build
docker compose run --rm paperful doctor
docker compose run --rm paperful run --collection interesting --dry-run
Or migrate into a sibling data directory (recommended if you use Compose long-term):
cp .env.example .env # PAPERFUL_DATA=../paperful-data
mkdir -p ../paperful-data/packs ../paperful-data/profiles ../paperful-data/out ../paperful-data/state
cp config.example.toml ../paperful-data/config.toml
# edit email / ezproxy_base / grey_playbooks_dir = "packs" as needed
# optional: move existing out/ and state/ into ../paperful-data/
docker compose build
docker compose run --rm paperful doctor
PAPERFUL_DATA in .env defaults to ../paperful-data. Compose mounts that
tree at /data inside the container. Use relative out_dir / state_dir /
grey_playbooks_dir in config (e.g. "out", "state", "packs") so they
resolve under the mounted data dir. Absolute host paths (e.g. /Users/...)
will not land on the volume.
Environment and override¶
File |
Role |
|---|---|
Copy to |
|
Base service (build, Zotero host, data volume) |
|
Optional local Compose tweaks |
.env and compose.override.yaml are gitignored so your machine-local paths
never land in the repo. Set PAPERFUL_DATA=. to keep config/out/state in the
repo; use ../paperful-data (default) to keep packs and outputs outside the
git root.
PAPERFUL_ZOTERO_HOST defaults to host.docker.internal so Docker Desktop
(macOS/Windows) can reach host Zotero. Compose also adds
extra_hosts: host.docker.internal:host-gateway for Linux Docker Engine.
paperful always sends Host: localhost:23119 — Zotero’s local API requires
that header even when the TCP peer is host.docker.internal.
Optional LLM inside the image¶
The image ships neither litellm nor browser-use, so the browser_agent lane
(run auto-recover and paperful recover) is host-only (it also needs the
headed-login vault). fix-metadata title
proposals, the lint identity check, summarize, and synthesize work from the container
against an Ollama running on the host:
[llm]
enabled = true
base_url = "http://host.docker.internal:11434"
allow_remote = true # host.docker.internal is not loopback
Start Ollama listening on all interfaces (OLLAMA_HOST=0.0.0.0 ollama serve).
docker compose run --rm paperful doctor shows the LLM row. Details:
LLM.
Custom playbook packs¶
Put extra grey-playbook TOML files in packs/ (under the data dir) and set in
config:
grey_playbooks_dir = "packs"
Merge order: builtin pack → packs/*.toml → inline [[grey_playbooks]]
(same name wins later). See Configuration.
Run configs¶
profiles/ next to config.toml holds named run configs (paperful all --profile, paperful profile save). That directory is not the grey-lit
packs/ folder and not state/packs/.
mkdir -p ../paperful-data/profiles
docker compose run --rm paperful profile list
docker compose run --rm paperful all --profile bbnj-journal --dry-run
See Workflows.
After doctor is green¶
Keep Zotero running. Bare docker compose run --rm paperful is doctor
(image CMD). To fetch:
docker compose run --rm paperful collections
docker compose run --rm paperful run --collection interesting --dry-run
docker compose run --rm paperful run --collection interesting
docker compose run --rm paperful report
--no-attach writes to out/ only. --library walks the whole library
(resumable; Ctrl-C then rerun). Same flags as Commands,
including --year-from / --year-to and --type / -T.
docker compose run --rm paperful run -C BBNJ --year-from 2023 -T journalArticle --dry-run
Common commands¶
docker compose run --rm paperful # doctor (default)
docker compose run --rm paperful doctor --no-guide
docker compose run --rm paperful collections
docker compose run --rm paperful dedupe -C BBNJ --dry-run
docker compose run --rm paperful run --collection interesting --dry-run
docker compose run --rm paperful run --collection interesting
docker compose run --rm paperful report
make docker-build
make docker-doctor
Pass any CLI flag after the service name; the image ENTRYPOINT is paperful.
The same commands as uv snippets — docker compose run --rm paperful
instead of uv run paperful. Headed session login is still host-only.
Image contents¶
Python 3.12, paperful + Playwright Chromium (htmlpdf / session vault reuse)
Poppler (
pdftotext)Non-root user
paperful(uid 1000)
If bind-mounted out/ / state/ are not writable, fix ownership on the host
(chown -R 1000:1000 …) or run with a matching user override.
Sessions and attach¶
Headed Chromium login and Zotero’s authorize dialog need the host GUI. Typical flow:
docker compose run --rm paperful doctor— on a TTY, amber session checks open a guide: runpaperful session login ezproxy/scholaron the host (samePAPERFUL_DATA/state/the container mounts), press Enter in the container to re-check. Or skip with--no-guide.Approve attach once on the host so
state/zotero-local-api-key.jsonexists.Run fetch/attach from the container as above.
Without the guide:
On the host (
uv):paperful session login ezproxyand/orscholar; approve attach once sostate/zotero-local-api-key.jsonexists.Ensure that
state/is the same tree the container mounts.Run fetch/attach from the container as above.
Usual path (uv)¶
uv sync --group dev
uv run pytest
uv run paperful doctor
See Commands, Architecture, and Zotero.