On this page

A service you cannot rebuild is a service you cannot trust with your documents. This self-hosted OCR service on a rented GPU server is built by script, rolled out from a clean commit and cross-checked by a second OCR engine.

Reproducible OCR setup: scripted build and one roll-out path

A fresh server is built by script with pinned versions. Code reaches a server only through one roll-out script, from a clean commit, and the revision can be read off the server. A mistake found before a customer meets it hardens the software. Every problem found is written down with where it was found and what was done.

How to run an OCR service on a rented GPU server: pinned pipelines, page count first, documents deleted

  • Work happens on a test server without customer data. Production changes only by script from a clean commit.
  • Access to a server is by SSH key.
  • The commit check decides. It checks the folder of the commit. Only a commit with a green check goes in.
  • Pinned, named reading modes. A pipeline stays as it is; a change ships as a new version.
  • The page count is known before the job. dry_run returns the page count first and the price calculated from it; at the start billing is off.
  • Customer data serves only your job. Documents are deleted when the job ends; the result is kept 24 hours or until you delete it.
Four steps in a row: build by script, roll out from a clean commit, acceptance run as a customer, every number tied to a run.
The path of a release: four steps, in this order.

Release checks for an OCR service: review, evidence per number, acceptance run

  • A second pair of eyes on every change. The person who builds hands over; someone else reads, adds tests and merges.
  • Every number on the site names its run. A number changes only through the table of evidence, where it carries its run.
  • An acceptance run through the deployed service, written from the point of view of a customer.

Problems the release checks found: rotated pages, claims, text-layer errors

  • A turned page moved every word. Offline, the text of a language model is merged with a character-level OCR engine. On one turned test page only 13 % of the words matched, because the places came from the original image and the lines from the upright one. After turning back and mapping the places over, 99 % matched. This two-engine method is measured offline on a small test set; the reading modes in the download are listed on the start page.
  • Every statement on the site follows the measurement. A second person checks each against the runs.
  • Numbers that looked like OCR errors. 355 and 405 are errors in the text layer of other people's PDFs and are labelled that way.

Setup problems on a rented GPU server and how each was fixed

  • The preferred card was fully booked. The larger cards could only be reserved, with a long wait and a long commitment. A card of the same generation was used, so nothing had to be rebuilt.
  • The build was slow, and the first fix was the wrong one. Downloads to the rented server were slow at first, and the environments were built one after another. A later test showed the network was faster than feared; the order was the bottleneck. The build now runs in parallel with the same pins and hashes. A cache of pinned files that can be copied over in one go exists too.
  • Scripts that hung or lied. One install waited ten minutes on two open connections without an error, and a setup reported FAIL although nothing was missing. Both are fixed: longer timeouts, retries, and each step checks only its own files. Shell scripts also needed one fixed line ending to run on Linux; a rule in the repository now sets it.
  • The service gave an empty answer. A page that took longer than a second to read got an empty success answer. The release check found it while setting up its test cases; it is fixed and covered by a test.
  • Headings lost their word places. In the combined reading mode a heading marker such as ## or a list dash has no place on the page. Because of that, every heading and every list item in hOCR and in the searchable PDF dropped to confidence 0 on its line and lost its word boxes, although all of its words were confirmed. Found by reading the output of an end-to-end test; the marker is no longer treated as page text, and a test fails on the old code. What stays by design: a single word that no second OCR engine confirmed still sets its line to confidence 0.

Two OCR engines on one page: word boxes and confidence score as a cross-check

On the hardest test pages, one pipeline with the character engine alone found 105 of 135 target lines (run on the hardest pages).

Outlines of word boxes on one test page; blue for words the second OCR engine confirmed, red for flagged words.
Two engines on one page: where they differ, the word is flagged.

Where the combined reading mode runs and how the first API key is made

  • The combined reading mode (text of a language model, confirmed word by word) runs on the GPU as a pipeline and is in the download.
  • A key for the hosted service comes on request, free: ask for it. On your own server the first key is made with the key tool that comes with the download.

On-premise OCR: run the software on your own server

You can download the software and run it on your own hardware: install it. For a dedicated server run for you, or for support and maintenance, see your own server: price on request.

Sources

Statement Where it comes from
13 % to 99 % of words matched offline experiment on merging model text and character-level reading, one turned page of the hard set
105 of 135 target lines run on the hardest test pages, one run of one pipeline
355 and 405 runs with the PDF text tool pdftotext and a second extraction, on the text layers of other people's PDFs
Wait for larger cards; parallel build; cache of pinned files notes from the go-live (setup and build), 2026-11-06; the cache was tried with test files only
Heading markers dropped lines to confidence 0 in hOCR and PDF review of the combined reading mode: the output of an end-to-end test, the fix and the test that fails on the old code