On this page
Somewhere in my house is the receipt for the dishwasher. I know I have it. It’s in a drawer, or a folder on some drive named scans_final_FINAL, or an email attachment from 2019. When the dishwasher broke, I spent longer looking for the receipt than the repair took. The problem was never that I didn’t have my documents — it’s that I couldn’t find the one I needed, because they were organized by “wherever I dropped them” and searchable by nothing.
Paperless-ngx is the cure, and it’s one of the most quietly life-improving things I run at home. You drop a document in, and it reads the text — even out of a photo or a scan with no text layer — and makes every word searchable. This is the third stop in the Self-Host the Apps You Actually Use series: after teaching my local AI to search the web and my bookmarks to tag themselves, this is the one my whole household actually uses.
Here’s the trick that makes it work — OCR on ingest:
The addresses below are examples. Replace 10.0.0.50 with your Paperless host’s real IP, and add any hostname you’ll actually use (a LAN name, a Tailscale address) to the allowed-hosts list. Choose your own admin username in place of youradmin and keep its password in your password manager, never in a note. If a value looks specific to one machine, it’s a placeholder to change — not a literal to copy.
What Paperless-ngx is (and what OCR buys you)
Paperless-ngx is a self-hosted document management system. The workflow is beautifully simple: you drop a file into a watched “consume” folder, and Paperless ingests it — extracting the text, guessing a title and date, and filing it with tags and document types you can define. From then on it’s searchable, taggable, and viewable from any browser.
The magic ingredient is OCR — optical character recognition. A scanned receipt or a photo of a letter is, to a computer, just a grid of colored dots; there’s no text in it to search. OCR (Paperless uses the excellent Tesseract engine under the hood) looks at those dots, recognizes the letters, and produces real, searchable text layered onto the document. That’s the difference between “I have 4,000 scans” and “I can find the one that mentions the word dishwasher.”
Deploy Paperless-ngx
Paperless runs as a small stack of containers with Docker Compose, and it’s happiest living on a NAS or a small always-on server where your files already are. The project ships an interactive install script that generates a compose setup by asking you a few questions — it’s the recommended starting point per the official setup docs.
On a host with Docker and the Compose plugin, run the bootstrapper. It’ll ask about your database, broker, folders, and ports, then write out a ready-to-run compose project.
bash -c "$(curl -L https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/install-paperless-ngx.sh)"
Two choices matter for a home setup. For the database, the project supports both PostgreSQL and SQLite; for a single-user household archive I pick SQLite — it’s one fewer container and backs up as a single file. For the broker (Paperless uses a task queue that needs a Redis-compatible service), the current official compose uses Valkey — the open-source Redis fork — and that’s what I run.
SQLite is great for one user. If you’ll have several people ingesting documents at once, or you’re importing tens of thousands of files, PostgreSQL is the more scalable database. You can start on SQLite and migrate later.
Leaving the image on :latest means an unattended pull could change your version out from under you. Pin it to a specific release in your compose file so upgrades are a deliberate act (read the release notes, then bump).
image: ghcr.io/paperless-ngx/paperless-ngx:3.0.5
This one cost me a confused ten minutes. GitHub releases are named like v3.0.5, but the container tag on GHCR is 3.0.5 — no v. Use :v3.0.5 and you get manifest unknown. Check the releases page for the current version and drop the v when you write the tag.
Paperless is a Django app, and Django is strict about which hostnames and origins it will answer to. If you skip this you’ll hit a login that fails with a host or CSRF error. Set both, listing every address you’ll reach it by:
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
They look like the same machine to you; to Django they’re two separate hostnames. List both, or a request that arrives as one while only the other is allowed gets a 400 Bad Request — which is easy to misread as “ingestion is broken” when your documents are actually importing fine. The configuration docs also offer a shortcut: setting PAPERLESS_URL can populate the allowed-hosts and trusted-origins together.
docker compose pull
docker compose up -d
Create the first superuser (the installer usually prompts for this, or you can set PAPERLESS_ADMIN_USER / PAPERLESS_ADMIN_PASSWORD in the env before first boot). Then open the web UI on your chosen port and log in.
The acceptance test that actually proves it works
It’s tempting to declare victory the moment the web UI loads. Don’t. The UI loading only proves the web server runs — it says nothing about whether OCR and search actually function end to end. Here’s the test I trust, and it’s deliberately mean: feed it an image-only document with no text layer at all, so OCR is the only way any text could exist to find.
I generated a one-page PDF containing an image of an invoice number and no embedded font, dropped it in the consume folder, waited for it to ingest, and then searched for that number. It came back. That single search proves ingest fired, Tesseract OCR’d the image, and the full-text index and search all work — none of which the loading web page could tell you.
Protect the archive from day one
There’s a special kind of pain in losing the archive you finally organized. Documents are precious and irreplaceable, so the moment Paperless is live, make sure its data and media folders are inside your backup scope — ideally before you import anything you care about. I deliberately put Paperless’s storage inside an existing nightly backup set so the archive was covered the hour it was born. A black-box monitoring probe on the web UI rounds it out, so a stalled service pages you instead of silently refusing new documents. The companion playbook has the full deploy.
What’s next
You can now find any document by a word printed inside it — no more drawer-diving. Next in the series, we stop searching for updates entirely and let the homelab watch pages for us: changedetection.io polls any web page and pings your phone only when something actually changes.
Related posts:
- Self-Hosted Bookmarks That Tag Themselves With Local AI — the previous post: make your saved links findable, too.
- Watch Any Web Page for Changes With changedetection.io — the next post: let a watcher poll pages so you don’t have to.
- Use a NAS With Your Homelab — the natural home for a document archive and its backups.
- Your First Docker Compose Stack — the deployment pattern Paperless uses.
- Deploy a Proxmox Backup Server — get the backup scope in place before you import documents you can’t lose.
- What Is Prometheus? — the monitoring that catches a silently-stalled service.
- Self-Host Your Photos With Immich — the same “own your important files” instinct, applied to photos.
Comments
Comments are powered by GitHub Discussions — sign in with a GitHub account to join the conversation.