# Readur turns a folder of scanned documents into a searchable archive

> Readur is a self-hosted document management system written in Rust with a React frontend, combining OCR, folder and WebDAV and S3 ingestion, and PostgreSQL full-text search. It suits a home or team archive that outgrew a directory of PDFs, and it demands a container runtime and a real database.

**readur/readur** — Quick, painless, intuitive OCR platform written in Rust and TypeScript. Modern UI with modern API, with an emphasis on intuitive user experience.

- Repository: https://github.com/readur/readur
- Stars: 801 · Forks: 81
- Language: Rust
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/readur-readur

## A document archive that reads its own scans

Readur is described as a document management system for individuals and teams who want to organise, process and reach their digital documents. The emphasis is on a modern API and a modern interface, and the implementation is Rust in the backend with TypeScript and React in the frontend.

The problem it solves is the one every paperless pile runs into. A directory of PDFs and phone photos is unsearchable, and re-running a separate OCR job every time you add a file is manual work. Readur combines the steps: upload or watch a source, extract the text automatically, keep the original file, and make the content searchable from the same interface.

The audience spans two scales. A household archiving receipts, manuals and scanned letters gets a private document system with a search box instead of a folder tree. A team that already syncs from WebDAV, a local network folder or S3-compatible storage gets ingestion, labels, health monitoring and a query language on top of the same pipeline.

What it is not is a note-taking tool or a PDF reader. The unit of work is a document that has been ingested and indexed, and the feature list in the README is organised around that: sources, OCR, search, labels, monitoring, notifications and an analytics dashboard for document statistics and processing status.

The project is MIT licensed and self-hosted, deployed with Docker Compose, and documented at docs.readur.app.

## Axum, SQLx and an OpenAPI surface in the Cargo manifest

The Cargo manifest gives a precise picture of the backend. The crate is readur, edition 2021, with a binary at src/main.rs and two additional binaries, test_runner and analyze-webdav-performance, which suggests WebDAV performance is something the project measures rather than assumes.

The web layer is axum with the multipart and websocket features enabled, served by tokio with the full feature set, with tower-http providing CORS, filesystem and header handling. Data access goes through sqlx with the postgres and sqlite drivers both enabled, along with the migrate feature, so database migrations are managed by the application rather than by hand.

Authentication is JWT-based with bcrypt password hashing, which matches the feature table's description of JWT-based authentication, and utoipa appears in the dependency list, which is the OpenAPI generator behind the built-in Swagger UI that the README says is reachable from the profile menu.

Two dependency choices are worth noting because they shape behaviour. tesseract is an optional feature, and so is the image crate together with imageproc and kamadak-exif, which means the image processing path is compiled in rather than shelled out. The notify crate provides file system watching, which is the mechanism behind the folder monitoring feature.

For a frontend that has to display many documents, the choice of sqlx means queries are checked against the database schema at compile time when a database is available, which catches a class of error before deployment rather than at runtime.

## Search modes backed by PostgreSQL full-text search

The search feature is described as PostgreSQL full-text search with multiple modes: simple, phrase, fuzzy and boolean. A dedicated guide covers search modes, syntax and optimization.

The modes differ in how forgiving they are. Simple search handles single terms, phrase search constrains the order and adjacency of words, fuzzy search tolerates near misses and typos, and boolean mode lets a user combine terms with operators. For a document archive, that combination is the point. A boolean query can require a word and exclude another, which is what separates a useful search from a pile of loosely related hits.

The dependency on PostgreSQL is not incidental. Full-text search with ranking, phrase matching and fuzzy behaviour is implemented in the database engine, and the compose file runs postgres:18 with a healthcheck using pg_isready. The sqlite driver is also compiled into the binary, so a lighter deployment is possible, but the features described in the README are the PostgreSQL ones.

Two related capabilities sit close by. The analytics dashboard reports document statistics and processing status, which is how an operator finds out that a source has stopped syncing. Health monitoring adds proactive source validation, so a broken WebDAV endpoint surfaces as a health signal rather than as an unexplained gap in results.

## Three ingestion paths and a watched folder

Documents arrive from more than one place, and the sources guide documents WebDAV, local folders and S3-compatible storage, with separate guides for general sources and for S3 setup. A migration guide exists for moving from local storage to S3, and a troubleshooting page for S3 problems.

Folder monitoring is the feature that makes ingestion continuous rather than manual. It is described as non-destructive file watching with intelligent sync scheduling, and the Dockerfile sets the permissions on the watch directory to 755 explicitly to handle various mount scenarios. The notify crate in the manifest is the Rust side of that.

The distinction between watching and syncing matters for how you configure it. A watch folder is where you drop a scan and it gets picked up; a source is a remote endpoint that is polled or pulled from. The compose file creates /app/uploads and /app/watch directories in the runtime image, so both exist as first-class mount points.

Non-destructive is the word to hold on to. The README describes the watching as non-destructive, and the whole design assumes the original file stays where it is and Readur keeps its own indexed copy. That is what makes the ingestion safe to point at a folder that is also being written to by another process, and it is also why the upload directory is a real disk path you will need to back up.

Notifications cover sync events, OCR completion and system status, so an ingestion failure is visible without polling the dashboard.

## Starting the stack and finding the generated admin password

The quick start is four commands, and the one worth reading closely is the clone. The README's snippet clones from a different path than the repository it lives in:

```bash
# Clone the repository
git clone https://github.com/perfectra1n/readur
cd readur

# Start all services
docker compose up --build -d

# Access the application
open http://localhost:8000
```

The comment above the start command explains the optional JWT_SECRET variable: it signs session tokens, and when it is unset a key is generated on first start and stored in the database. The commented line shows how to manage it yourself with openssl rand -hex 32 written into a .env file. The compose file also exposes a rotate-jwt-secret command for changing it later.

The admin account is where most first runs trip. The username is admin unless ADMIN_USERNAME overrides it, and the password comes from ADMIN_PASSWORD if that variable is set. If it is not set, Readur generates a random 24-character password on first startup and writes it to a file named initial-admin-password in the .readur directory inside UPLOAD_PATH, which is /app/uploads/.readur/initial-admin-password in the Docker image. The file is readable only by the server user and the password is deliberately not written to the logs; the log line shows the path instead. You read it with:

```bash
docker compose exec readur cat /app/uploads/.readur/initial-admin-password
```

Then you sign in, change the password and delete the file. If you need to do that again later, there is a CLI command:

```bash
docker compose exec readur readur reset-admin-password
```

The compose file also exposes postgres on port 5432 with credentials of readur for all three fields, which is a development default you will want to change before exposing anything.

## OCR runs on Tesseract inside the container image

Text extraction uses Tesseract together with Office document parsing, and there is a dedicated optimization guide for OCR performance and a separate guide for multi-language processing.

The container image is where the OCR choices become concrete. The build stage installs tesseract-ocr and tesseract-ocr-all, along with libtesseract-dev and libleptonica-dev for compiling the Rust binding, and the runtime stage installs tesseract-ocr with the full set of language packs again, plus poppler-utils for pdftotext. It also installs ghostscript, unpaper and pngquant, and then ocrmypdf itself through pip3, and adds antiword and catdoc for legacy DOC files.

That list tells you what the pipeline can do. Text layers are extracted from PDFs with poppler, and images are handled by Tesseract with every language pack present, so multi-language documents can be processed in several languages at once with automatic language detection. Legacy Word formats are converted rather than rejected.

Two things are absent, and they are worth noticing. There is no PDF rendering stack of the kind a viewer needs, so Readur is a text extraction and indexing system rather than a document previewer, and the image processing crates in the Cargo manifest are optional, which means the pipeline is assembled from pieces rather than being one fixed configuration.

The multi-language feature has an obvious cost that the guides address: running several languages at once multiplies the OCR work per page. That is an argument for configuring language sets deliberately rather than enabling everything.

## Readur against a separate OCR job and a file server

The alternative approach is the one most people start with: keep the documents in a file server or a synced folder, and run a standalone OCR tool over it when you want text, writing the results into sidecar files or into a search index you maintain yourself.

The difference is where the state lives. In the split approach the document, the extracted text and the search index are three things that can disagree with each other, and keeping them in sync is your problem. Readur holds all three in one system, with the file, the extracted text and the indexed content updated as one pipeline, and it exposes that pipeline through an API, a Swagger UI and a set of source types.

The trade is control and cost. The split approach uses tools you already have, can be tuned per document type, and does not require running a database and a Rust service. Readur asks for a container runtime, a PostgreSQL instance, disk for uploads and an eye on the health of each source.

There is also a middle option worth naming. Because Readur watches folders, you can point it at a folder that another tool already syncs, and keep the search index in Readur while leaving the authoritative copies in your own storage. The README documents the migration path from local storage to S3, which suggests that shape of deployment is expected.

For an evaluation, the fair question is how many documents you have and how often they change. A folder of receipts that grows slowly does not need a document management system, and a shared archive that grows daily does.

## MIT licensing, release automation and an active main branch

Readur is distributed under the MIT licence, with the text in the LICENSE file at the repository root. That is permissive enough to run it internally, modify it, or base another product on it without licensing obligations beyond keeping the notice.

The maintenance record is strong on paper. The repository is not archived, the last push was on 2026-10-19, and the release list shows version 3.1.0 on 2026-10-13, version 3.0.0 on 2026-10-02 and version 2.9.2 on 2026-07-09. A 3.0.0 landing within three weeks of a 2.9.2 line suggests a substantial API change, which is worth noting for anyone planning to pin a version.

The tooling in the tree reflects a serious engineering setup. There are separate compose files for development and for tests, a Dockerfile.dev and a Dockerfile.stress, a migrations directory, a Makefile, a run-tests.sh script, a lefthook configuration with lefthook.toml and lefthookrc, a .mise directory for tool versions, clippy.toml for lint rules, and a .renovaterc.json5 for dependency updates. release-please with a manifest and config handles versioning and changelog generation, and there are separate Grafana dashboard JSON and nginx configuration files for deployment.

The docs are built with MkDocs and the Material theme, driven by mkdocs.yml, and the requirements file lists the theme extensions and the plugins for redirects, RSS and image handling.

Upgrade cost is therefore mostly operational: a new major version means checking migrations and the environment variable reference, and the configuration reference is the document to diff first.

## Conclusion

Adopt Readur when you want a self-hosted archive where scanned documents become searchable text, and you already run containers and can accept PostgreSQL as the store. Do not adopt it for a handful of files, and treat the OCR layer as a text extractor rather than a document renderer, since the runtime image installs Tesseract and poppler rather than a PDF viewer stack. Verify first that your documents are within the supported upload types, that your S3 or WebDAV source is reachable from the container, and that the generated admin password file is deleted after the first sign-in. The last push was on 2026-10-19 and version 3.1.0 was released on 2026-10-13.

## FAQ

### How do I install and start Readur?

Clone the repository, then run docker compose up --build -d and open http://localhost:8000. The quick start also documents an optional JWT_SECRET, and a separate command can be used to reset the admin password later with docker compose exec readur readur reset-admin-password.

### Where is the initial admin password stored?

If ADMIN_PASSWORD is not set, Readur generates a random 24-character password on first startup and writes it to initial-admin-password in the .readur directory inside UPLOAD_PATH, which is /app/uploads/.readur/initial-admin-password in the image. Read it with docker compose exec readur cat /app/uploads/.readur/initial-admin-password, then change the password and delete the file.

### What OCR engine does Readur use?

Tesseract, together with Office document parsing. The container image installs tesseract-ocr with all language packs, poppler-utils for pdftotext, and ocrmypdf, plus antiword and catdoc for legacy DOC files, which is what allows multi-language OCR with automatic language detection.

### Can Readur search documents from S3 or WebDAV?

Yes. Sources cover WebDAV, local folders and S3-compatible storage, with separate guides for sources and for S3 setup. Folder monitoring adds non-destructive file watching with intelligent sync scheduling.

### What licence is Readur released under?

MIT, with the terms in the LICENSE file at the repository root. The last push was on 2026-10-19 and version 3.1.0 was released on 2026-10-13, following version 3.0.0 on 2026-10-02.

## Sources

- [Issues](https://github.com/readur/readur/issues)
- [License: MIT](https://github.com/readur/readur/blob/main/LICENSE)
- [README](https://github.com/readur/readur/blob/main/README.md)
- [readur/readur on GitHub](https://github.com/readur/readur)
- [Releases](https://github.com/readur/readur/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/readur-readur
