Deploy Paperless-ngx on a NAS (OCR Document Archive)
Stand up Paperless-ngx with Docker Compose on a NAS or small server: SQLite + Valkey, the allowed-hosts fix, and a forced-OCR acceptance probe that proves search works.
DistrosDebian 12, Ubuntu 24.04
Shell
bashUpdated
Script
# Deploy Paperless-ngx (OCR document management) with Docker Compose.
# Full walkthrough + the acceptance test: /blog/paperless-ngx-document-archive
# Requires Docker + Compose v2 on the host (a NAS or small always-on server).
# 1. Run the OFFICIAL interactive installer. It asks about database, broker,
# folders and ports, then writes a ready-to-run compose project.
bash -c "$(curl -L https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/install-paperless-ngx.sh)"
# - Database: SQLite is fine for a single-user archive (one fewer container,
# single-file backups). Choose PostgreSQL if you expect many users / heavy ingest.
# - Broker: a Redis-compatible service; the current official compose uses Valkey.
# 2. In the generated docker-compose.yml, PIN the image to a real release.
# GHCR DROPS the release 'v' prefix: use 3.0.5, NOT v3.0.5 (v = manifest unknown).
# Check https://github.com/paperless-ngx/paperless-ngx/releases for the current tag.
# image: ghcr.io/paperless-ngx/paperless-ngx:3.0.5
# 3. In the generated env file, set allowed hosts + trusted origins. Django treats
# localhost and 127.0.0.1 as DIFFERENT hosts, so list BOTH plus every real address.
cat >> docker-compose.env <<'EOF'
PAPERLESS_ALLOWED_HOSTS=localhost,127.0.0.1,10.0.0.50,paperless.homelab.lan
PAPERLESS_CSRF_TRUSTED_ORIGINS=http://localhost:8020,http://127.0.0.1:8020,http://10.0.0.50:8020
PAPERLESS_ADMIN_USER=youradmin
# Set PAPERLESS_ADMIN_PASSWORD from your secret store before first boot, not here.
EOF
# 4. Bring it up.
docker compose pull
docker compose up -d
# 5. ACCEPTANCE TEST (the honest one): feed it an IMAGE-ONLY PDF with no text
# layer, so OCR is the only way any text can exist, then search for a word
# printed only in the image. If it's found, ingest + OCR + search all work.
# - Drop the image-only PDF into the consume folder (e.g. ./consume/).
# - Wait for it to disappear (ingested), then search that word in the UI.
What this does
This deploys Paperless-ngx — a self-hosted document management system with OCR — using its official interactive installer, which generates a Docker Compose project. Once running, you drop PDFs or scanned images into a watched consume folder and Paperless runs Tesseract OCR to make the text searchable. It’s an ideal service for a NAS, where your documents and backups already live.
The full walkthrough, including the two Django gotchas and the acceptance test, is in Paperless-ngx: OCR Every Document You Own, Searchable.
Prerequisites
- A host with Docker and the Docker Compose v2 plugin — a NAS that supports Docker, or a small Linux server.
- Enough disk for your documents plus their OCR’d copies and thumbnails. Documents are the point; give them room.
- Somewhere to keep the admin password — a password manager, not a note.
Notes
- Make these values your own before you rely on the result: replace
10.0.0.50with your Paperless host’s real IP,paperless.homelab.lanwith your real hostname,youradminwith your chosen admin username, and set the admin password from your secret store (never paste a real password into the env file in a repo). Add any Tailscale or LAN address you’ll use to the allowed-hosts list. If a value looks specific to one machine, it’s a placeholder to change, not a literal to copy. - The GHCR tag has no
v. Releases are namedv3.0.5; the container tag is3.0.5. Using:v3.0.5yieldsmanifest unknown. Pin a real release and re-check the releases page before bumping. localhost≠127.0.0.1to Django. List both inPAPERLESS_ALLOWED_HOSTS(and matchingPAPERLESS_CSRF_TRUSTED_ORIGINS) or a login/POST can400— which is easy to misread as an ingestion failure when documents are actually importing fine.PAPERLESS_URLcan set these together per the configuration docs.- SQLite vs PostgreSQL is a real choice. SQLite is simplest for one user and backs up as a single file; PostgreSQL scales better for many users or heavy ingestion. You can migrate later.
- Back up
data/andmedia/from day one. These hold your archive; put them in your backup scope before importing anything you can’t afford to lose. - Trust the forced-OCR acceptance test, not the loading UI. A web page that loads only proves the web server runs. An image-only PDF whose printed text becomes searchable proves ingest, OCR, and search all work end to end.