MaruOpsInfrastructure and AI notes

About (Korean)Contact (Korean)

[Python Environments 06] Structure OCR Tools, Language Data, APIs and Workers

Workspace with laptop, camera, and tablet

Reference photo · Workspace with laptop, camera, and tablet · Photo: Héctor Martínez oria_hector / Source / CC0 · Resized for listings; illustrative photo, not the tested environment

Installing pytesseract alone can still leave an executable-not-found error. It is a Python wrapper around Tesseract; the OCR engine and language data must also be installed. Use the OCR example to separate OS executables, model data, Python dependencies and worker processes.

Python development environment · 01 selection and venv · 02 uv · 03 conda · 04 web Development·05 Data·AI·06 OCR·Service Separation

On this page

Materials required and installation location for OCR

materialexampleInstallation/storage location
Python codeCLI, API, workerproject source
Python packagePillow, pytesseractProject environment of uv·venv or conda
OCR executableTesseractThis article is installed as an OS package
PDF conversion toolpdftoppm by PopplerThis article is installed as an OS package
language dataeng, kor traineddatatessdata location that Tesseract searches for
input/output fileDocument/Image/Extracted TextSeparate storage space with defined permissions and storage period

Below is a small CPU CLI based on Tesseract. If you use a different engine such as PaddleOCR or EasyOCR, the installation conditions for the framework and model files will also be different. If comparing multiple engines, check compatibility conditions in each engine project rather than installing them all in one environment from the beginning. It is based on pytesseract description and Tesseract installation guide.

1. Install OCR engine and language pack for each OS.

Tesseract language pack for Ubuntu 24.04

sudo apt update
sudo apt install -y tesseract-ocr tesseract-ocr-eng tesseract-ocr-kor poppler-utils fonts-dejavu-core
tesseract --version
tesseract --list-langs
pdftoppm -v

Install engines and language packs on Rocky Linux 9

sudo dnf install -y dnf-plugins-core epel-release
sudo dnf config-manager --set-enabled crb
sudo dnf install -y tesseract tesseract-langpack-eng tesseract-langpack-kor poppler-utils dejavu-sans-fonts
tesseract --version
tesseract --list-langs
pdftoppm -v

Rocky checks for required repositories and native dependencies. This example is based on an environment where EPEL and CRB are available. If your organization applies a separate storage policy, prepare the package according to that policy. You can also check the Rocky Linux EPEL guide.

Proceed after checking whether eng and kor are present in --list-langs. It is also possible to select conda-forge’s Tesseract·Poppler, but you must re-check the executable file path in the environment and whether language data is included. Don’t assume that you have Korean data ready just because you installed the conda package.

2. Create a Python OCR project

ocr-demo/
├── pyproject.toml
├── uv.lock
├── .python-version
├── src/ocr_demo/
│   ├── __init__.py
│   └── cli.py
├── tools/make_test_image.py
├── tests/test_runtime.py
├── inputs/
└── outputs/
[project]
name = "ocr-demo"
version = "0.1.0"
requires-python = ">=3.12,<3.13"
dependencies = ["pillow>=10,<13", "pytesseract>=0.3.13,<0.4"]

[dependency-groups]
dev = ["pytest>=8,<10", "ruff>=0.11,<1"]

[build-system]
requires = ["hatchling>=1.27,<2"]
build-backend = "hatchling.build"

Create src/ocr_demo/__init__.py as an empty file, and save the following contents in cli.py. You can receive the input/output path and language as arguments and adjust them to suit the execution location.

import argparse
from pathlib import Path

import pytesseract
from PIL import Image


def recognize(source: Path, destination: Path, language: str) -> None:
    with Image.open(source) as image:
        image.load()
        text = pytesseract.image_to_string(image, lang=language, timeout=30)
    destination.parent.mkdir(parents=True, exist_ok=True)
    destination.write_text(text, encoding="utf-8")


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("source", type=Path)
    parser.add_argument("destination", type=Path)
    parser.add_argument("--lang", default="eng+kor")
    args = parser.parse_args()
    recognize(args.source, args.destination, args.lang)
uv python pin 3.12
uv lock
uv sync --locked
uv run --locked python -c 'import shutil; print(shutil.which("tesseract"))'

inputs/sample.png contains small, non-sensitive image files that you prepare yourself. Searching for executable files follows the PATH of the process, so it may differ between terminal and service environments. If installed in a different location, the path must be specified in the service PATH or pytesseract settings.

# inputs/sample.png를 준비한 뒤 실행
uv run --locked python -m ocr_demo.cli inputs/sample.png outputs/sample.txt --lang eng+kor
cat outputs/sample.txt

A small test image to check if your installation is OK.

Initially, it is best to isolate installation errors from input document quality issues. tools/make_test_image.py below creates a test input that draws an English sentence with the DejaVu font installed by the OS. It is not a benchmark that measures OCR accuracy on real documents.

from pathlib import Path

from PIL import Image, ImageDraw, ImageFont

font_candidates = [
    Path("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"),
    Path("/usr/share/fonts/dejavu-sans-fonts/DejaVuSans.ttf"),
]
font_path = next(path for path in font_candidates if path.exists())
image = Image.new("RGB", (900, 160), "white")
ImageDraw.Draw(image).text((20, 40), "HELLO PYTHON", fill="black",
                          font=ImageFont.truetype(str(font_path), 64))
Path("inputs").mkdir(exist_ok=True)
image.save("inputs/sample.png")
uv run --locked python tools/make_test_image.py
uv run --locked python -m ocr_demo.cli inputs/sample.png outputs/sample.txt --lang eng
cat outputs/sample.txt

tests/test_runtime.py

import shutil
import subprocess


def test_tesseract_languages():
    binary = shutil.which("tesseract")
    assert binary is not None
    result = subprocess.run([binary, "--list-langs"], check=True,
                            capture_output=True, text=True)
    languages = (result.stdout + result.stderr).splitlines()
    assert "eng" in languages
    assert "kor" in languages
uv run --locked python -m pytest

This test checks whether the executable file and English/Korean data are visible. We added a separate English test image to the OCR CLI to check its operation. This is not a test that verifies the recognition rate of real Korean documents, handwriting, tables, and rotated documents.

3. PDF processing also records tools outside the Python environment

mkdir -p outputs
pdftoppm -f 1 -singlefile -r 150 -png inputs/sample.pdf outputs/page-1
uv run --locked python -m ocr_demo.cli outputs/page-1.png outputs/page-1.txt --lang eng+kor

This example converts the first page of a prepared PDF into an image. Increasing rendering resolution can also increase processing time and memory. The actual service that accepts external document uploads places separate restrictions on file size, format, number of pixels, and processing time. Simply pasting the CLI example onto the web does not satisfy the service operation conditions.

4. Time to divide the API and OCR worker environments

document-service/
├── services/
│   ├── api/
│   │   ├── pyproject.toml
│   │   ├── uv.lock
│   │   └── src/document_api/
│   └── ocr-worker/
│       ├── pyproject.toml
│       ├── uv.lock
│       └── src/document_ocr/
├── contracts/
├── infra/
└── README.md

For small personal programs, one environment may be sufficient. If the file upload API and OCR processing increase independently or a GPU engine is added, the API and workers can be divided into separate environments and processes, and the task ID, input location, and result format can be connected with a contract. The API process does not need to import the entire learning framework.

project structureenvironmental border
A script or twoOne project, one environment. Record specifications and implementation methods
Single web app/CLI packageDivided into src and tests, but with one runtime environment. Development tools are categorized into groups
Multiple Python packages compatible with each otherCommon lock management possible with uv workspace. Dependency compatibility is a prerequisite
Conflicting services such as API and GPU workersIndependent pyproject and locking/execution environment. Separate work units and communication contracts
frontend and backendManage environment and lock files of Node and Python respectively

Monorepo is a storage arrangement method and is not a rule that enforces a common environment. uv workspace is intended for members to share locks and virtual environments, and may not be suitable when there are conflicting requirements or when an independent virtual environment for each member is desired. uv workspace document also explains this limitation and independent project/path dependency alternatives.

5. Confirm that CI is restored in the new environment

# 새 체크아웃의 각 Python 서비스 디렉터리에서
uv sync --locked --no-default-groups --group dev
uv run --locked --no-default-groups --group dev python -m pytest
uv run --locked --no-default-groups --group dev ruff check src tests

If it is an OCR service, you must prepare Tesseract, language pack, and Poppler for CI images. Download cache and specification-based regeneration are given priority over the cache that copies .venv, and OS·architecture·Python·uv version and lock file are included in the cache conditions. If you use Jenkins, you can prepare the same OS package in the agent of existing Pipeline article and then run this command from each service directory.

What to check when the environment is messed up

  1. Check the actual Python path with sys.executable.
  2. Check the installation area with the corresponding tool: python -m pip --version, uv pip check, or conda list.
  3. Check specifications and lock files. Just install manually and check if there are any packages whose specifications are missing.
  4. Look for external executables as command -v or shutil.which.
  5. Check conditions outside the environment such as language data, model files, shared libraries, and drivers.
  6. Reconfigure the specifications in a new directory/new environment to see if the same problem reproduces.

This series was written on October 18, 2026, based on collating official documentation and checking CPU-based examples in Ubuntu 24.04·Rocky Linux 9 containers. The conda environment creation/analysis package import was confirmed in a separate isolation path. The verification scope does not include full VM configuration, systemd operation deployment, actual GPU learning/inference, and OCR quality of real Korean documents.

Download series example file·venv·conda specification, uv·web·data·OCR example source, and confirmed uv lock file are bundled. It is executed from each project directory, and the OS package is prepared separately through the procedures in the main text.