On Runtime, OCR pays for the CPU it uses and nothing while it waits. 2,000 PDFs a month, with 30 CPU-seconds of extraction and OCR each, cost $1.08 on a 2 vCPU, 4 GiB sandbox, at the rates Runtime publishes, read 26 October 2026. Every file is parsed in a Firecracker microVM with its own kernel. A malformed or malicious PDF from a user therefore never touches your application server.
Two kinds of PDF, two tools
A PDF made by a word processor carries its text. Extraction reads that text back. A scanned PDF is a picture of a page. It has no text until OCR recognizes it. pypdf's documentation says as much: a page that holds only an image gives little or no text. OCR software such as Tesseract is the tool for that case (pypdf docs, read 26 October 2026).
| Tool | What it does | Ubuntu 24.04 package |
|---|---|---|
pdftotext |
Embedded text to a plain-text file | poppler-utils 24.02.0 |
pdftoppm |
Each page to a PNG image, for OCR or a vision model | poppler-utils 24.02.0 |
pdfinfo, pdfimages |
Metadata and page count; the images inside the file | poppler-utils 24.02.0 |
tesseract |
Text from an image; with pdf, a searchable PDF |
tesseract-ocr 5.3.4 |
| pypdf | Page-by-page text from Python, page.extract_text() |
pip install pypdf |
The versions are Launchpad's package versions, read 26 October 2026.
Build the image once
TypeScriptimport { Runtime } from "withruntime";const runtime = new Runtime();await runtime.images.build({ name: "pdf", recipe: { apt: ["poppler-utils", "tesseract-ocr"], pip: ["pypdf"] },});Pythonfrom withruntime import RuntimeRuntime().images.build(name="pdf", recipe={"apt": ["poppler-utils", "tesseract-ocr"], "pip": ["pypdf"]})Extract, and fall back to OCR
Try the embedded text first. If it comes back nearly empty, render the pages to images and recognize them.
TypeScriptimport { readFile } from "node:fs/promises";import { Runtime } from "withruntime";const runtime = new Runtime();await using sbx = await runtime.sandboxes.create({ image: "pdf", network: { internet: false }, timeoutSeconds: 900, onLeaseEnd: "stop",});await sbx.files.write("/workspace/doc.pdf", await readFile("invoice.pdf"));await sbx.exec(["pdftotext", "doc.pdf", "doc.txt"], { check: true, timeoutMs: 120_000 });let text = await sbx.files.readText("/workspace/doc.txt");if (text.trim().length < 50) { // A scan: pages to PNG at 300 dpi, then OCR each one. await sbx.exec("pdftoppm -r 300 -png doc.pdf page", { check: true, timeoutMs: 300_000 }); await sbx.exec('for f in page-*.png; do tesseract "$f" "${f%.png}"; done', { timeoutMs: 900_000, }); const parts = (await sbx.files.glob("page-*.txt")).sort(); const pages = await Promise.all(parts.map((p) => sbx.files.readText(`/workspace/${p}`))); text = pages.join("\n\f");}console.log(text.slice(0, 500));Pythonfrom withruntime import Runtimeruntime = Runtime()with runtime.sandboxes.create( image="pdf", network={"internet": False}, timeout_seconds=900, on_lease_end="stop",) as sbx: with open("invoice.pdf", "rb") as source: sbx.files.write("/workspace/doc.pdf", source.read()) sbx.exec(["pdftotext", "doc.pdf", "doc.txt"], check=True, timeout_ms=120_000) text = sbx.files.read_text("/workspace/doc.txt") if len(text.strip()) < 50: # A scan: pages to PNG at 300 dpi, then OCR each one. sbx.exec("pdftoppm -r 300 -png doc.pdf page", check=True, timeout_ms=300_000) sbx.exec('for f in page-*.png; do tesseract "$f" "${f%.png}"; done', timeout_ms=900_000) parts = sorted(sbx.files.glob("page-*.txt")) text = "\n\f".join(sbx.files.read_text(f"/workspace/{p}") for p in parts) print(text[:500])The 50-character threshold is a starting point. Tune it on your own documents.
By default, tesseract imagename outputbase writes outputbase.txt in English
(Tesseract's documentation, read 26 October 2026). Other languages need their
data packages installed in the image.
Pages for a vision model
Some documents are better read by a model that sees the page, such as forms,
tables with merged cells and handwriting. The same pdftoppm step produces one
PNG per page. Read them back with files.read and send them to your model. Only
the images leave the sandbox, and only because your code chose to fetch them.
Let an agent write the parser
For a stack of similar documents, such as a supplier's invoices, a model can write a small pypdf or regex script that pulls out the fields. That script is untrusted code. Run it in the same sandbox, with the internet already off. Check its JSON against your schema outside the sandbox before you store anything. The loop is the one in AI data pipelines: generate, run with a time limit, validate, retry with the error.
What PDF work needs from its sandbox
| Need | How Runtime covers it |
|---|---|
| Parsing files from strangers | A microVM with its own kernel; the host enforces network and resources |
| No exfiltration of the contents | Internet off at create; root in the guest cannot turn it back on |
| OCR's CPU bursts | Shared CPU bursts up to the sandbox's vCPUs; you pay for what is measured |
| Big scans | Uploads in 1 MiB pieces, each checked by SHA-256; set diskMiB for room |
| A page that makes OCR hang | timeoutMs per command, 60 seconds by default and up to 24 hours |
| Documents already in a bucket | Mount S3, R2 or Google Cloud Storage read-only as a directory |
| Many files at the same time | 100 sandboxes at the same time on a paid account |
What it costs
Take 2,000 PDFs a month, a mix of digital files and scans. Each one gets a 2 vCPU, 4 GiB sandbox for 40 seconds. Extraction and OCR use 30 CPU-seconds:
- CPU
- 2,000 × 30 s / 3,600 × $0.025
- $0.42
- Memory
- 2,000 × 40 s / 3,600 × 4 GiB × $0.0075
- $0.67
- Total
- $1.08
The CPU line is measured use, at $0.025 per vCPU-hour. The memory line is $0.0075 per GiB-hour while the sandbox runs (pricing). Digital PDFs that need no OCR finish in a few seconds and cost less. Tesseract is free software, so there is no per-page OCR fee on top. A new account gets 100 free hours of a 1 GB machine in its first 30 days, which covers the first experiments, with no card.
Sources
- pypdf: extract text:
PdfReader,extract_text()and the note on scanned pages, read 26 October 2026. - Tesseract command-line usage:
tesseract imagename outputbaseand searchable PDF output, read 26 October 2026. - Poppler and Launchpad's poppler-utils and tesseract-ocr pages for Ubuntu 24.04, read 26 October 2026.
Related: document conversion, data analysis agent, run untrusted LLM code safely, Python in a sandbox.
Facts on this page were checked on 26 October 2026.