| .forgejo/workflows | ||
| backend | ||
| frontend | ||
| scripts | ||
| shared | ||
| .dockerignore | ||
| .env.example | ||
| .gitattributes | ||
| .gitignore | ||
| docker-compose.yml | ||
| docker-entrypoint.sh | ||
| Dockerfile | ||
| openapi.json | ||
| openapi_pretty.json | ||
| Project Architecture.md | ||
| README.md | ||
PaperJet
PaperJet is a self-hosted, single-user PDF annotation editor. It keeps the uploaded PDF immutable, stores annotations separately, autosaves the working layer, and flattens supported annotations only when exporting.
Run with Docker
- Copy
.env.exampleto.env. - Replace
SECRET_KEYwith a long random value. Compose refuses to start without it. - Build and start the single-container application:
docker compose up --build -d
The container serves the React application through nginx and proxies /api
requests to the loopback-only FastAPI process. Open http://localhost:4982
(or the value of HTTP_PORT). The first visit asks you to create the single
master password.
The db, pdf_storage, and thumbnails volumes contain application data.
Back them up before maintenance or upgrades. Put the frontend behind HTTPS
when exposing PaperJet beyond localhost and set COOKIE_SECURE=true.
Run a Forgejo-published image
Set PAPERJET_IMAGE in .env to the desired registry tag, for example the
latest or commit-SHA tag published by Forgejo. Then pull and start without
building locally:
docker compose pull
docker compose up -d --no-build
The published image exposes only port 80. Your outer reverse proxy should
target the Compose port configured by HTTP_PORT.
Development
Backend:
cd backend
pip install -e ".[dev]"
uvicorn app.main:app --reload
Frontend, in a second terminal:
cd frontend
npm ci
npm run dev
The Vite server proxies /api to http://localhost:8000.
Validation
# container
docker compose --env-file .env.example config --quiet
docker compose up --build -d
curl http://localhost:4982/api/v1/health
docker compose ps
# frontend
cd frontend
npm test -- --run
npm run typecheck
npm run lint
npm run build
# backend
cd ../backend
pytest -q
mypy app/
ruff check .
Stop the local stack with docker compose down. Do not use -v unless you
intend to remove the local application volumes.
The editor uses PDF.js for display and PyMuPDF for export. Stored annotation coordinates are PDF points with a top-left origin relative to the unrotated CropBox; this is the contract shared by the editor and export renderer.