Доставка Python-приложения: wheel, контейнер или бинарник
Три способа довезти Python-код до сервера или пользователя. У каждого своя цена: размер, воспроизводимость, скорость старта и головная боль с зависимостями. Разбираем, когда что выбирать.

Один и тот же Python-сервис можно доставить тремя способами: собрать wheel и поставить через pip, упаковать в Docker-образ или заморозить в один исполняемый файл. Выбор не косметический. Он определяет размер артефакта (от сотен килобайт до сотен мегабайт), скорость холодного старта, воспроизводимость сборки и то, что сломается на чужой машине с другой libc. Разберём цену каждого подхода и критерий выбора.
Речь про Python 3.11–3.12, pip 24.x, современный сборочный фронтенд (build, uv или poetry) и упаковку по PEP 517/518 через pyproject.toml. Устаревший python setup.py bdist_wheel напрямую вызывать не нужно — фронтенд делает это за вас в изолированном окружении.
Способ 1: wheel — родной формат Python
Wheel1 — это zip-архив с уже готовыми к установке файлами. Никакой компиляции при установке (для чистого Python), быстрый pip install, стандартный механизм зависимостей. Это способ по умолчанию, если ваш потребитель — другой Python-проект или сервер, где Python уже есть.
Минимальный pyproject.toml под сборочный бэкенд hatchling:
# pyproject.toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "orders-service"
version = "1.4.0"
requires-python = ">=3.11"
dependencies = [
"httpx>=0.27",
"pydantic>=2.6",
]
[project.scripts]
orders = "orders_service.cli:main"
Сборка (пакет build версии 1.x):
# установка сборочного фронтенда
python -m pip install build
# сборка sdist и wheel в каталог dist/
python -m build
# в dist/ появятся два файла:
# orders_service-1.4.0-py3-none-any.whl
# orders_service-1.4.0.tar.gz
Тег py3-none-any означает чистый Python без привязки к платформе — такой wheel ставится везде. Если в проекте есть C-расширения, тег будет вида cp312-cp312-manylinux_2_28_x86_64: он привязан к версии Python и к ABI системных библиотек. Собирать такие wheel для чужих дистрибутивов нужно в образах manylinux, иначе на сервере с другой glibc установка упадёт.
Что вы платите за wheel
На целевой машине должен быть совместимый Python и доступ к индексу пакетов (PyPI или приватный). Зависимости тянутся при установке — если PyPI недоступен или пакет выпилили, установка не воспроизведётся. Версии транзитивных зависимостей не заморожены самим wheel: для воспроизводимости нужен отдельный lock-файл (uv.lock, poetry.lock или requirements.txt с хешами).
Способ 2: контейнер — доставка вместе с окружением
Docker-образ везёт не только ваш код, но и интерпретатор, системные библиотеки и точную версию каждой зависимости. Это устраняет класс проблем «на моей машине работало». Плата — размер (типичный Python-образ 150–400 МБ) и то, что теперь вы отвечаете за базовый образ и его уязвимости.
Многоступенчатая сборка отделяет установку зависимостей от финального образа, чтобы не тащить в прод компилятор и кеши:
# Dockerfile, база python:3.12-slim
FROM python:3.12-slim AS builder
# ставим зависимости в отдельный каталог
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN pip install uv && \
uv export --frozen --no-dev -o requirements.txt && \
pip install --prefix=/install -r requirements.txt
# финальный образ без сборочных инструментов
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /install /usr/local
COPY src/ ./src/
# запуск не от root
RUN useradd --create-home appuser
USER appuser
CMD ["python", "-m", "orders_service"]
Сборка и проверка размера:
docker build -t orders-service:1.4.0 .
docker images orders-service:1.4.0
# запуск
docker run --rm -p 8000:8000 orders-service:1.4.0
Ключевое отличие от wheel: воспроизводимость близка к абсолютной, потому что образ фиксирует всё до системных библиотек. Если lock-файл заморожен (uv export --frozen), пересборка через год даст тот же набор пакетов.
Контейнер не делает сборку воспроизводимой сам по себе. RUN pip install httpx без lock-файла притянет свежую версию при каждой пересборке — образ будет «воспроизводимым» ровно до следующего релиза httpx.
Про slim и alpine
База python:3.12-slim собрана на Debian с glibc — wheel из manylinux ставятся без пересборки. python:3.12-alpine использует musl libc: для пакетов с C-расширениями manylinux-wheel не подойдёт, pip начнёт собирать из исходников, и вам понадобятся gcc и заголовки в образе. Alpine даёт меньший размер, но платите временем сборки и отладкой. Для большинства сервисов slim — разумный дефолт.
Способ 3: бинарник — один файл без Python на машине
Если на целевой машине Python ставить нельзя или не хочется (десктоп-утилита, CLI для не-разработчиков, изолированный сервер), код замораживают в один исполняемый файл. Инструменты: PyInstaller и Nuitka. PyInstaller упаковывает интерпретатор и байткод в архив, который распаковывается во временный каталог при запуске. Nuitka компилирует Python в C и даёт настоящий нативный бинарник.
Сборка через PyInstaller (версия 6.x):
pip install pyinstaller
# один файл, без консольного окна для GUI убрать --console
pyinstaller --onefile --name orders src/orders_service/cli.py
# результат в dist/orders (Linux/macOS) или dist/orders.exe (Windows)
./dist/orders --help
Флаг --onefile собирает всё в один файл; без него получите каталог с бинарником и папкой зависимостей — он стартует быстрее, потому что не распаковывается во временный каталог при каждом запуске.
Что ломается с бинарниками
- Кроссплатформенность отсутствует. Бинарник собирается под ту ОС и архитектуру, где вы его собрали. Нужен exe под Windows — собирайте на Windows (или в её окружении), под ARM — на ARM.
- Скрытый импорт. Если модуль импортируется динамически (через
importlibили строку), PyInstaller его не увидит и не положит в сборку — при запуске получитеModuleNotFoundError. Лечится флагом--hidden-import имя_модуля. - Файлы данных. Шаблоны, JSON-конфиги, статика внутри пакета не попадут в бинарник автоматически. Их добавляют через
--add-data, а в коде путь ищут относительноsys._MEIPASSдля PyInstaller. - Антивирусы. onefile-бинарники PyInstaller регулярно ловятся на ложные срабатывания эвристики.
Сравнение
| Критерий | wheel | контейнер | бинарник |
|---|---|---|---|
| Размер артефакта | КБ–единицы МБ | 150–400 МБ | десятки МБ |
| Нужен Python на цели | да | нет (нужен runtime) | нет |
| Нужен доступ к PyPI при установке | да | нет | нет |
| Воспроизводимость | с lock-файлом | высокая | средняя |
| Скорость холодного старта | мгновенно | секунды (образ) | задержка на распаковку (onefile) |
| Кроссплатформенность | py3-none-any — да | по архитектуре образа | нет, сборка под каждую ОС |
Когда что выбирать
- wheel — потребитель ваш же Python-проект, библиотека для установки через pip, внутренний сервис на машине с управляемым Python и приватным индексом. Самый лёгкий и быстрый путь.
- контейнер — сервис в оркестраторе (Kubernetes, Nomad), нужна одинаковая среда на всех стендах, зависимости с системными библиотеками. Дефолт для серверного бэкенда.
- бинарник — CLI или десктоп-утилита для пользователей без Python, поставка на изолированный хост, где нельзя ставить интерпретатор. Не для веб-сервисов в кластере.
Что делать дальше
Зафиксируйте зависимости в lock-файле — без него ни один из трёх способов не даст повторяемую сборку. Для контейнеров прогоните образ через сканер уязвимостей (trivy, grype) и проверьте, что процесс не идёт от root. Для бинарников соберите на чистой машине и запустите на целевой ОС без установленного Python — только так убедитесь, что ничего не подтянулось из вашего окружения. Точный синтаксис флагов сверяйте с документацией используемой версии инструмента: у PyInstaller и uv он менялся между мажорными релизами.
Логотип и фирменный стиль: Три концепции разного характера, палитра с обоснованием, проверка на читаемость в малом размере и в один цвет.
Спросить за 60 ₽Источники: Python Packaging User Guide — Packaging Python Projects, PyInstaller Manual
Частые вопросы
У меня в проекте есть C-расширения — какой wheel собирать для сервера?
Почему бинарник PyInstaller падает с ModuleNotFoundError, хотя всё работало из-под python?
Slim или alpine для Docker-образа Python?
Достаточно ли контейнера для воспроизводимой сборки?
Можно ли собрать бинарник под Windows на Linux?
Что выбрать для внутреннего веб-сервиса, который крутится в Kubernetes?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.