Paperwise is an open-source, self-hosted document intelligence app for OCR, metadata extraction, document organization, and grounded Q&A.
Website: paperwise.dev
Docs:
- Docker Engine or Docker Desktop with Compose support
- At least one model provider key for metadata extraction and grounded Q&A
- Optional:
tesseractif you want OCR to stay local instead of using an LLM for OCR
The GitHub Actions publish workflow pushes images to:
ghcr.io/zellux/paperwise
Create a docker-compose.yml file:
services:
api:
image: ghcr.io/zellux/paperwise:latest
environment:
PAPERWISE_ENV: docker
PAPERWISE_LOG_LEVEL: INFO
PAPERWISE_API_HOST: 0.0.0.0
PAPERWISE_API_PORT: 8000
PAPERWISE_REDIS_URL: redis://redis:6379/0
PAPERWISE_REPOSITORY_BACKEND: postgres
PAPERWISE_POSTGRES_URL: postgresql+psycopg://paperwise:paperwise@postgres:5432/paperwise
PAPERWISE_OBJECT_STORE_ROOT: /data/object-store
PAPERWISE_AUTH_SECRET: replace-with-a-strong-secret
PAPERWISE_SESSION_TTL_SECONDS: "43200"
PAPERWISE_SESSION_COOKIE_SECURE: "false"
PAPERWISE_DISABLE_SIGNUP: "false"
depends_on:
redis:
condition: service_healthy
postgres:
condition: service_healthy
ports:
- "8080:8000"
volumes:
- paperwise_data:/data
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/healthz', timeout=3)"]
interval: 15s
timeout: 5s
retries: 5
start_period: 20s
restart: unless-stopped
worker:
image: ghcr.io/zellux/paperwise:latest
command: ["sh", "-c", "exec celery -A paperwise.workers.celery_app.celery_app worker --loglevel=INFO ${PAPERWISE_WORKER_CONCURRENCY:+--concurrency=${PAPERWISE_WORKER_CONCURRENCY}}"]
environment:
PAPERWISE_ENV: docker
PAPERWISE_LOG_LEVEL: INFO
PAPERWISE_REDIS_URL: redis://redis:6379/0
PAPERWISE_REPOSITORY_BACKEND: postgres
PAPERWISE_POSTGRES_URL: postgresql+psycopg://paperwise:paperwise@postgres:5432/paperwise
PAPERWISE_OBJECT_STORE_ROOT: /data/object-store
PAPERWISE_AUTH_SECRET: replace-with-a-strong-secret
PAPERWISE_SESSION_TTL_SECONDS: "43200"
PAPERWISE_SESSION_COOKIE_SECURE: "false"
PAPERWISE_DISABLE_SIGNUP: "false"
depends_on:
redis:
condition: service_healthy
postgres:
condition: service_healthy
volumes:
- paperwise_data:/data
restart: unless-stopped
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 5
restart: unless-stopped
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: paperwise
POSTGRES_PASSWORD: paperwise
POSTGRES_DB: paperwise
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U paperwise -d paperwise"]
interval: 10s
timeout: 3s
retries: 5
restart: unless-stopped
volumes:
paperwise_data:
postgres_data:Start the stack:
docker compose up -dBefore starting, replace replace-with-a-strong-secret with your own secret in both api and worker.
The example is configured for direct HTTP access at http://localhost:8080. If you put Paperwise behind HTTPS, set PAPERWISE_SESSION_COOKIE_SECURE to "true" in both api and worker.
Set PAPERWISE_DISABLE_SIGNUP to "true" on the api service to disable public self-service account registration while keeping login available for existing users.
Set PAPERWISE_WORKER_CONCURRENCY to a positive integer before running docker compose up -d if you need to limit worker parallelism for a local or rate-limited model backend, for example PAPERWISE_WORKER_CONCURRENCY=1. When unset, Celery keeps its default concurrency behavior.
If the GHCR package is private, make it public in the GitHub package settings before sharing it with other users.
Open Paperwise at http://localhost:8080.
Paperwise exposes a Paperless-compatible API for the open-source Paperless Mobile app. Point the mobile app at your Paperwise server URL and sign in with your Paperwise account to browse, search, filter, and upload documents.
Compatibility currently targets the core mobile workflows rather than the complete Paperless-ngx API surface.
For local development after cloning the repo:
git clone https://github.com/zellux/paperwise.git
cd paperwise
make setup
make dev-upOpen the app at http://localhost:8000.
Useful local commands:
make dev-status
make dev-stop
uv run pytestThe local dev stack starts the FastAPI app, Celery worker, Redis, and Postgres. Uploaded files and local runtime data are stored under local/.
For local source runs, set PAPERWISE_WORKER_CONCURRENCY before make worker or make dev-up to limit Celery worker concurrency.
Before you hand this off to other users, it is worth checking five things on a clean deploy:
- Sign up and sign in both work.
- Saving Settings > Model Config succeeds.
- Uploading a document moves it out of
processing. - The worker is running and document jobs complete.
- Ask My Docs returns an answer for at least one test document.
New images are published automatically when changes land on main.
To update a running server that uses ghcr.io/zellux/paperwise:latest:
docker compose pull
docker compose up -dBack up both persistent data locations:
postgres_datafor users, preferences, document records, and search metadatapaperwise_datafor uploaded files and extracted object-store data
If you only back up one of them, restore will be incomplete.
For a safe upgrade path:
- Back up
postgres_data. - Back up
paperwise_data. - Run
docker compose pull. - Run
docker compose up -d.
After the app is running:
- Create your first account.
- Open Settings > Model Config.
- Add an OpenAI, Gemini, or Custom (OpenAI-compatible) connection.
- Assign models for:
- Metadata Extraction
- Grounded Q&A
- OCR
- Upload a few documents and test extraction or Ask My Docs.
If Paperwise runs in Docker and your local model server runs on the Docker host, do not use localhost in Model Config. Use host.docker.internal, for example http://host.docker.internal:1234/v1. Both the api and worker containers need access because settings tests run in the API and document processing runs in the worker.
- Metadata extraction: a fast general-purpose model
- Grounded Q&A: a stronger reasoning model if you need cross-document answers
- OCR:
- use LLM OCR for scans, forms, or image-heavy PDFs
- use Local Tesseract if you want OCR to stay on your machine
For more detail, use the docs:
docker compose ps
docker compose logs -f api worker
docker compose logs -f postgres redis
docker compose pull
docker compose downIf sign-in succeeds but the app immediately shows the sign-in screen again:
- if you serve Paperwise over plain HTTP, set
PAPERWISE_SESSION_COOKIE_SECUREto"false"in bothapiandworker - if you serve Paperwise behind HTTPS, set
PAPERWISE_SESSION_COOKIE_SECUREto"true"in bothapiandworker - run
docker compose pull - run
docker compose up -dso the containers are recreated with the new image and environment - clear the site's browser cookies if an old session is still cached
This happens when a browser receives a Secure session cookie over HTTP. The browser will not send that cookie back, so login appears to work but the next request is unauthenticated. New Paperwise images support PAPERWISE_SESSION_COOKIE_SECURE; already-deployed hosts still need their local docker-compose.yml and running containers updated.
If uploads stay stuck in processing:
- check that the
workercontainer is running - inspect
docker compose logs -f api worker - make sure Redis is healthy and reachable
If CPU usage spikes while documents are processing:
- local OCR is the most likely cause, especially when Local Tesseract is enabled
- set
PAPERWISE_WORKER_CONCURRENCYto a positive integer, such as1, before starting the worker if your local LLM or OCR backend cannot handle Celery's default parallelism - open Settings > Model Config
- switch OCR Engine away from Local Tesseract, or disable OCR auto-switch if you do not want Paperwise to fall back to local OCR
- use an LLM OCR route instead if you prefer to trade provider calls for lower local CPU load
If a new image was published but your server still looks old:
- run
docker compose pull - then run
docker compose up -d
If upload works but extraction or Ask My Docs fails:
- open Settings > Model Config
- confirm the required task models are assigned
- check the API logs for provider timeout or auth errors
- for Docker plus host-local model servers such as LM Studio, Ollama-compatible gateways, or llama.cpp, use
host.docker.internalinstead oflocalhost
See LICENSE.