# How to run Tesseract OCR in a cloud sandbox Install tesseract-ocr and a language pack in a microVM, run tesseract or ocrmypdf on the scan, and read back text, TSV or a searchable PDF. **On Runtime, OCR runs next to the documents and nowhere near your servers.** Scans arrive from customers, email and crawlers; every sandbox is a Firecracker microVM with its own kernel, so the image decoders that read them work on a machine holding only that batch. OCR keeps CPUs busy, and Runtime bills what it uses: a 2 vCPU, 4 GiB sandbox costs $0.08 an hour at full load, $0.03125 an hour idle ([pricing](/docs/pricing)). Ubuntu 24.04 shipped Tesseract 5.3.4 and OCRmyPDF 15.2.0 on 25 September 2026. ## Versions and packages | What | Ubuntu 24.04 | Upstream on 25 September 2026 | | ----------------------------- | ------------------------------------------------------ | ------------------------------- | | Tesseract engine | `tesseract-ocr` 5.3.4 | 5.5.3 on GitHub | | Language data | `tesseract-ocr-eng`, `-deu`, `-fra`, ... (`1:4.1.0-2`) | tessdata repositories on GitHub | | Scanned PDF to searchable PDF | `ocrmypdf` 15.2.0 | 17.12.1 on PyPI | | Python wrapper | none | `pytesseract` 0.3.13 on PyPI | | In-browser or Node, no binary | none | `tesseract.js` 7.0.0 on npm | Language packs are named `tesseract-ocr-` plus Tesseract's three-letter code: `tesseract-ocr-deu` for German, `tesseract-ocr-fra` for French. ## Read the text in an image ```ts check import { Sandbox } from "withruntime"; await using sbx = await Sandbox.create({ timeoutSeconds: 1800 }); await sbx.exec( "sudo apt-get update -qq && sudo apt-get install -y -qq tesseract-ocr tesseract-ocr-eng tesseract-ocr-deu", { check: true, timeoutMs: 600_000 }, ); await sbx.network.off(); // the scans stay on this machine await sbx.files.upload("./invoice.png", "/workspace/invoice.png"); const text = await sbx.exec(["tesseract", "invoice.png", "-", "-l", "eng+deu", "--psm", "3"], { check: true, timeoutMs: 300_000, }); console.log(text.stdout); await sbx.exec(["tesseract", "invoice.png", "invoice", "-l", "eng", "tsv"], { check: true }); const tsv = await sbx.files.readText("/workspace/invoice.tsv"); const words = tsv .split("\n") .slice(1) .map((line) => line.split("\t")) .filter((cols) => cols.length === 12 && cols[11]!.trim() && Number(cols[10]) > 80); console.log(words.length, "words above 80% confidence"); ``` ```python check import csv import io from withruntime import Sandbox with Sandbox.create(timeout_seconds=1800) as sbx: sbx.exec("sudo apt-get update -qq && sudo apt-get install -y -qq " "tesseract-ocr tesseract-ocr-eng tesseract-ocr-deu", check=True, timeout_ms=600_000) sbx.network.off() # the scans stay on this machine sbx.files.upload("invoice.png", "/workspace/invoice.png") print(sbx.exec(["tesseract", "invoice.png", "-", "-l", "eng+deu", "--psm", "3"], check=True, timeout_ms=300_000).stdout) sbx.exec(["tesseract", "invoice.png", "invoice", "-l", "eng", "tsv"], check=True) rows = csv.DictReader(io.StringIO(sbx.files.read_text("/workspace/invoice.tsv")), delimiter="\t", quoting=csv.QUOTE_NONE) words = [r for r in rows if r["text"] and r["text"].strip() and float(r["conf"]) > 80] print(len(words), "words above 80% confidence") ``` - `-` as the output base sends the text to stdout; a result keeps up to 64 KiB, so write long documents to a file and read them with `files.readText`. - `-l eng+deu` combines languages for mixed documents. - The `tsv` output gives every word with its box and confidence, which is what an agent needs to find a total or a date by position. - The commands are arrays, so a file name cannot inject shell syntax. ## Output formats From Tesseract's command-line documentation, read 25 September 2026: | Output config | File written | Use | | ------------- | ------------ | -------------------------------------------------- | | (none) | `out.txt` | Plain text | | `pdf` | `out.pdf` | The image with an invisible, searchable text layer | | `hocr` | `out.hocr` | HTML with word boxes | | `tsv` | `out.tsv` | One row per word: position, confidence, text | `--psm` sets page segmentation: `3` is automatic, `6` assumes one uniform block of text. `tesseract --list-langs` shows the installed languages. ## Make a scanned PDF searchable Tesseract reads images, not PDFs. OCRmyPDF does the whole job: it renders the pages, runs Tesseract, and writes a PDF with a text layer, plus a plain-text sidecar if you ask. ```ts check import { Sandbox } from "withruntime"; await using sbx = await Sandbox.create({ image: "ocr", diskMiB: 6144, timeoutSeconds: 3600 }); await sbx.files.upload("./scan.pdf", "/workspace/scan.pdf"); await sbx.exec( "ocrmypdf -l eng --deskew --rotate-pages --skip-text --sidecar scan.txt scan.pdf searchable.pdf", { check: true, timeoutMs: 1_800_000 }, ); await sbx.files.download("/workspace/searchable.pdf", "./searchable.pdf"); await sbx.files.download("/workspace/scan.txt", "./scan.txt"); ``` The flags are from OCRmyPDF's cookbook: `--deskew` straightens tilted pages, `--rotate-pages` fixes pages scanned sideways, and `--skip-text` leaves alone pages that already have text. `--force-ocr` does the opposite. For OCRmyPDF 17 instead of Ubuntu's 15, its installation guide says to install the Ubuntu package for the dependencies, then `uv tool install ocrmypdf`. uv is already in every sandbox, and it installs to `/workspace/.local/bin`, first on `PATH`. ## Start every sandbox with OCR installed The `ocr` image the PDF sample uses, as a [custom image](/docs/images); add a `tesseract-ocr-` package per language you read: ```ts check import { Runtime } from "withruntime"; const runtime = new Runtime(); await runtime.images.build({ name: "ocr", recipe: { apt: ["tesseract-ocr", "tesseract-ocr-eng", "tesseract-ocr-fra", "ocrmypdf"], pip: ["pytesseract==0.3.13"], }, }); ``` ```python check from withruntime import Runtime Runtime().images.build(name="ocr", recipe={ "apt": ["tesseract-ocr", "tesseract-ocr-eng", "tesseract-ocr-fra", "ocrmypdf"], "pip": ["pytesseract==0.3.13"], }) ``` Building is free and uses no trial hours; the trial stores three images free. ## Throughput and limits - **CPU.** OCR is CPU-bound. Split a large archive across sandboxes, one batch each: a paid account runs 100 at once to start, the free trial eight. - **Disk.** Rendered PDF pages take room. The default 4 GiB disk had about 2.5 GiB free on 24 September 2026; ask for `diskMiB: 6144` or more for big PDFs, up to 10 GiB on the trial. - **Time.** A command's default timeout is 60 seconds. Long PDFs want `timeoutMs`, and the sandbox a `timeoutSeconds` longer than the job. To feed the text to a model that answers questions about it, see [a data analysis agent](/use-cases/data-analysis-agent). To turn Office files into PDFs first, see [LibreOffice in a sandbox](/integrations/libreoffice); to clean up images before OCR, see [ImageMagick in a sandbox](/integrations/imagemagick). ## Sources - Tesseract command-line usage, https://tesseract-ocr.github.io/tessdoc/Command-Line-Usage.html, read 25 September 2026 - Tesseract releases, https://github.com/tesseract-ocr/tesseract/releases/latest, read 25 September 2026 - OCRmyPDF cookbook and installation, https://ocrmypdf.readthedocs.io/en/latest/cookbook.html and https://ocrmypdf.readthedocs.io/en/latest/installation.html, read 25 September 2026 - Ubuntu 24.04 tesseract-ocr, tesseract-ocr-eng and ocrmypdf packages, https://packages.ubuntu.com/noble/, read 25 September 2026 - PyPI ocrmypdf and pytesseract, npm tesseract.js, read 25 September 2026 Facts on this page were checked on 25 September 2026.