Каждый раз, когда нужно задеплоить Python-приложение не в контейнер, а просто скопировать один файл на сервер, всплывает проблема зависимостей от системных библиотек. PyInstaller собирает бинарник, но он всё равно тащит libc, libssl и другие .so-файлы. На старой Ubuntu или в Docker на scratch это приводит к ошибкам при запуске. Опытные разработчики часто забывают, что статическая сборка решает эту проблему радикально.
Почему одного PyInstaller недостаточно
PyInstaller с флагом --onefile упаковывает Python-интерпретатор и код в один файл, но он динамически линкует системные библиотеки. Проверка через ldd покажет кучу зависимостей. На машине с другой версией glibc или отсутствующей библиотекой (например, libcrypto) приложение упадёт. Это типичная ошибка: разработчик уверен, что бинарник самодостаточен, но на проде получает segfault.
Решение: статическая линковка через staticx
staticx переупаковывает полученный PyInstaller-бинарник, заменяя динамические зависимости на статические. Он встраивает musl libc и делает бинарник полностью независимым от системы. Для этого:
pyinstaller --onefile --name myapp main.py
pip install staticx
staticx dist/myapp dist/myapp_static
На выходе — бинарник, который запускается на любом Linux с ядром >= 2.6.28. Без Python, без pip, без системных библиотек.
Типичные ошибки и trade-offs
* Размер бинарника: 5-30 МБ вместо 5 МБ. Для большинства серверных задач это приемлемо, но для embedded может быть критично.
* Проблемы с glibc-расширениями: staticx использует musl libc, поэтому старые C-расширения (например, NumPy, некоторые ORM) могут сломаться. Решение — собирать всё в Docker на alpine:latest с musl-dev.
* Динамические ctypes: если в коде используются ctypes для загрузки .so, их нужно вручную добавить через --add-binary, иначе приложение упадёт с тишиной.
* Дебаг: ошибки сложнее диагностировать. Помогает флаг --debug в PyInstaller и strace на скомпилированном бинарнике.
Production-пример: CI/CD деплой в корпоративной сети
Представьте: вы разворачиваете сервис на серверах, где IT-отдел одобряет установку только через внутренний репозиторий, а на копирование одного файла — без ограничений. Вы запускаете в CI:
pyinstaller --onefile --name service main.py
staticx dist/service dist/service_static
Копируете service_static на сервер — и он работает сразу, без Python, зависимостей и permission battles. Это особенно ценно для edge-устройств и минималистичных Docker-образов на scratch.
Вывод: staticx превращает PyInstaller-бинарник в truly portable решение, но требует учёта C-расширений и динамических загрузок — без этого сборка будет работать нестабильно на проде.