Skip to main content
Developer guide

Run Tesseract as an API
or self-hosted service.

Wrap Tesseract in an HTTP service with confidence: a production FastAPI endpoint, process-pool concurrency, a Docker image with language packs and health checks, scaling and backpressure, and serverless deployment — with privacy and cost trade-offs against managed OCR APIs.

TL;DR — a minimal service

Wrap the CLI or the pytesseract binding behind HTTP. The image never leaves your infrastructure.

bash
pip install fastapi uvicorn python-multipart pytesseract pillow
# install the engine + language packs first (see the install guide)
uvicorn app:app --host 0.0.0.0 --port 8000

Then call it: curl -F "file=@invoice.png" "http://localhost:8000/ocr?lang=eng". Tesseract itself is installed per the installation guide.

Design decisions first

Three choices shape the whole service.

DecisionOptionsGuidance
Sync vs asyncBlocking request vs job queueSync for single images under a few seconds; async job IDs for large or batched documents.
InputMultipart file upload vs base64 JSONPrefer multipart for binary images; base64 only when the client cannot upload files (adds ~33% size).
OutputPlain text, JSON, hOCR/ALTO, searchable PDFReturn JSON by default; offer hOCR for layout and a PDF download URL for archives.

One primary intent per endpoint. Keep /ocr returning text, and add separate endpoints for PDFs and layout data rather than overloading one response.

A production Python service (FastAPI)

Validation, size limits, a timeout, and OCR off the event loop.

app.py
import asyncio, io
from concurrent.futures import ProcessPoolExecutor
from fastapi import FastAPI, File, UploadFile, HTTPException, Query
from PIL import Image
import pytesseract

app = FastAPI(title="Tesseract OCR API")
pool = ProcessPoolExecutor(max_workers=4)
MAX_BYTES = 10 * 1024 * 1024  # 10 MB
LANGS = {"eng", "deu", "fra", "spa"}

def _ocr(data: bytes, lang: str) -> str:
    image = Image.open(io.BytesIO(data))
    return pytesseract.image_to_string(image, lang=lang)

@app.get("/health")
def health():
    return {"status": "ok"}

@app.post("/ocr")
async def ocr(file: UploadFile = File(...), lang: str = Query("eng")):
    if lang not in LANGS:
        raise HTTPException(400, "Unsupported language")
    data = await file.read()
    if len(data) > MAX_BYTES:
        raise HTTPException(413, "File too large")
    loop = asyncio.get_running_loop()
    try:
        text = await asyncio.wait_for(loop.run_in_executor(pool, _ocr, data, lang), timeout=30)
    except asyncio.TimeoutError:
        raise HTTPException(504, "OCR timed out")
    return {"text": text, "lang": lang}

Key details: allow-list languages, enforce a byte limit before decoding, run OCR in a process pool, and cap the wall-clock time. See the Python guide for the full pytesseract API.

Containerize it

Bake Tesseract and the language packs into the image so replicas are identical.

Dockerfile
FROM python:3.12-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
      tesseract-ocr tesseract-ocr-eng tesseract-ocr-deu \
      libgl1 libglib2.0-0 && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
EXPOSE 8000
HEALTHCHECK CMD python -c "import urllib.request;urllib.request.urlopen('http://localhost:8000/health')"
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "2"]

Add only the language packs you need to keep the image small. The install guide covers Docker and tessdata placement in more depth.

Concurrency & scaling

OCR is CPU-bound — parallelise with processes, not threads.

Security & privacy

Self-hosting is the main reason teams pick Tesseract.

Serverless & container platforms

Self-hosted Tesseract vs managed OCR APIs

DimensionSelf-hosted TesseractManaged OCR API
CostFixed infrastructure; no per-page feePay per request/page; scales with volume
PrivacyDocuments never leave your networkDocuments sent to a third party
AccuracyStrong on clean print; tunable per domainOften stronger on hard scans, handwriting
ControlFull: versions, languages, custom modelsVendor roadmap and quotas
OpsYou run it: scaling, upgrades, monitoringFully managed

Many teams self-host Tesseract for high-volume, privacy-sensitive work and fall back to a managed API only for the hard minority of documents.

API FAQ

How do I run Tesseract OCR as an API?

Wrap the CLI or pytesseract in FastAPI/Flask, expose POST /ocr, and containerize it. Images stay in your infrastructure.

Threads or processes?

Processes. OCR is CPU-bound and the GIL blocks thread parallelism; use ProcessPoolExecutor or CLI subprocesses.

Is a self-hosted API private?

Yes — uploaded images are processed locally and never sent to a third party, unlike managed OCR APIs.

Can it run serverless?

Yes, on container platforms like Cloud Run, Lambda container images or Fly.io; mind cold starts and image size with language packs.

What formats can I return?

Plain text, JSON with confidence, hOCR/ALTO, or a searchable PDF via a download URL.