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
| material | example | Installation/storage location |
|---|---|---|
| Python code | CLI, API, worker | project source |
| Python package | Pillow, pytesseract | Project environment of uv·venv or conda |
| OCR executable | Tesseract | This article is installed as an OS package |
| PDF conversion tool | pdftoppm by Poppler | This article is installed as an OS package |
| language data | eng, kor traineddata | tessdata location that Tesseract searches for |
| input/output file | Document/Image/Extracted Text | Separate 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 structure | environmental border |
|---|---|
| A script or two | One project, one environment. Record specifications and implementation methods |
| Single web app/CLI package | Divided into src and tests, but with one runtime environment. Development tools are categorized into groups |
| Multiple Python packages compatible with each other | Common lock management possible with uv workspace. Dependency compatibility is a prerequisite |
| Conflicting services such as API and GPU workers | Independent pyproject and locking/execution environment. Separate work units and communication contracts |
| frontend and backend | Manage 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
- Check the actual Python path with
sys.executable. - Check the installation area with the corresponding tool:
python -m pip --version,uv pip check, orconda list. - Check specifications and lock files. Just install manually and check if there are any packages whose specifications are missing.
- Look for external executables as
command -vorshutil.which. - Check conditions outside the environment such as language data, model files, shared libraries, and drivers.
- 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.



