Skip to content

Quickstart

The tooling on this page exists now

deploy/bootstrap/install.sh and deploy/compose/scan-bridge.yml are in the repository. What has not been done is a run of this page end to end on a fresh Pi: the pipeline itself is proven against the reference hardware, and the bootstrap script is proven by its own --dry-run and by docker compose config, but nobody has yet taken an unprepared machine from nothing to a scan by following these six steps. Expect to hit something. Please report it.

Prerequisites

  • A Linux host running Docker, within USB reach of the scanner. Any amd64 or arm64 machine. A Raspberry Pi 4 or 5 on Ubuntu Server 24.04 is the reference, and it is the cheap way to put a host wherever the scanner has to stand — but it is one option, not a requirement. If you already run a Docker host within cable reach of where the scanner will live, use that and skip the Pi entirely.
  • A SANE-compatible USB scanner — see the hardware list
  • A Synology NAS with NFS enabled
  • A Docker host for Paperless-ngx (can be the NAS)

The host needs Docker, an NFS mount and USB permissions. Everything else runs in containers.

Scanned pages never touch the host's disk

Every scan writes raw TIFF pages, has them read back by scan-processor, and deletes them again before the HTTP response is sent — they exist for the duration of one request and no longer. The reference stack puts that scratch space on tmpfs, so it lives in RAM and never reaches durable storage.

That matters most on a Pi booting from an SD card, where a write-erase cycle per scan is the access pattern those cards tolerate worst — but it is the right default everywhere, because the data has no reason to be written to a disk it is about to be erased from. Sizes are SCAN_BRIDGE_SCRATCH_SIZE and SCAN_PROCESSOR_TMPFS_SIZE in .env; each holds one job at a time, and an overrun fails loudly rather than producing a short document.

Finished documents go to the NFS share, not to the host.

1. Prepare the Synology share

Create a shared folder for the scan pipeline and enable NFS access for the Pi's IP address. The exact export options depend on which storage topology you pick; Topology B (NFS direct) is the simplest starting point.

2. Bootstrap the Pi

Download the script, read it, then run it. It modifies /etc/fstab and /etc/udev/rules.d/ as root, so piping it straight into a shell is not worth the convenience — a truncated download would execute as a half-script.

ssh pi@your-pi-host
curl -fsSLO https://raw.githubusercontent.com/strausmann/paperless-scan-bridge/main/deploy/bootstrap/install.sh
less install.sh          # read what it is about to do
sudo bash install.sh

The script installs Docker and the compose plugin, adds the NFS mount to /etc/fstab, installs the udev rule that gives the container stable access to the scanner, and pulls the container images. It touches nothing else on the host.

3. Configure

git clone https://github.com/strausmann/paperless-scan-bridge.git
cd paperless-scan-bridge

cp deploy/compose/.env.example deploy/compose/.env
$EDITOR deploy/compose/.env

# the two secrets, as files -- an env var shows up in `docker inspect`
printf '%s' 'YOUR_PAPERLESS_TOKEN' > deploy/secrets/paperless_api_token
openssl rand -hex 32               > deploy/secrets/bridge_token
# 0644, not 0640: compose passes the host file's ownership into the
# container, and scan-bridge runs as UID 65532, not as you.
chmod 0644 deploy/secrets/*

At minimum you set the Paperless-ngx URL, the API token, and the NFS mount point.

Do not commit secrets

The Paperless API token and the bridge's own tokens belong in Docker secrets, environment variables, or a SOPS-encrypted file — never in a profile YAML and never in git. Profile files reference secrets by name only; the daemon rejects cleartext tokens at parse time.

4. Bring up the bridge

docker compose -f deploy/compose/scan-bridge.yml up -d

PSB_VERSION in .env has no default: compose refuses to start without it rather than reaching for latest (ADR 0011), so an unpinned deployment cannot happen by forgetting.

Pin an explicit image version in your compose file. This project does not publish or use latest tags.

5. Verify

curl -s http://your-host:18080/ready
curl -s http://your-host:18080/profiles

/health reports process liveness. /profiles lists the configured scan profiles. Both endpoints work today.

6. First scan

curl -X POST http://your-host:18080/scan \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  -d '{"profile": "default"}'

This works today — the deployment tooling around it does not

POST /scan is a real, bearer-protected handler: it dispatches through sane-runtime to the scanner, has scan-processor assemble the pages, and delivers the result to the configured destinations. It was first driven end to end against the reference hardware on 2026-08-26. The /jobs* endpoints still return 501 Not Implemented — asynchronous job tracking is separate work.

What is missing is everything around it on this page: the bootstrap script and the published compose stacks.

Troubleshooting

If the scanner is not detected, start at Troubleshooting.