Packaging Python Applications with PyInstaller for Linux
Learn how to use PyInstaller to bundle your Python application and its dependencies into a single package for Linux distribution. This guide covers setup, packaging, and testing across different Linux distributions.
Introduction
In this article, we will learn how to package a Python application using PyInstaller for Linux. PyInstaller is a tool that bundles a Python application and its dependencies into a single package. This package can be distributed and run on any Linux system without requiring the installation of Python or the application's dependencies. This makes it easy to distribute Python applications to users who do not have Python installed on their system. PyInstaller supports multiple platforms including Linux, Windows, and macOS.
Understanding PyInstaller and Its Limitations
PyInstaller is a versatile tool that analyzes your Python application and gather all necessary modules and libraries it needs to run. It then packages them into a single executable file or a package.
However, it's important to understand some of the limitations:
- System Dependencies: While PyInstaller bundles Python depencencies, it does not bundle system-level dependencies. This means that if your application requires system-level dependencies, you need to ensure that they are present on the target system. On linux, the package requires certain versions for system libraries like
glibc
,musl
(for Alpine), etc. to be present on the target system. - Architecture Specifity: PyInstaller does not support cross-compilation. This means that the package built on one architecture (e.g.
x86_64
) will not run on another architecture (e.g.arm64
). You need to build the package on the target architecture to run it on that architecture.
Prerequisites
To follow all the steps in this article, you need to have the following prerequisites:
- A Linux system with Docker installed and running.
x86_64
architecture is recommended as PyInstaller does not support cross-compilation for other architectures.- A Python application that you want to package.
- Basic knowledge of Docker and Python.
Workflow Overview
Setting Up the Base Docker Image
The first step is to create a base Docker image that contains the necessary dependencies to build the Python application.
Now, if you see Ubuntu-24.04, it has a glibc
with version 2.39
, Ubuntu-14.04 has 2.19
, and Debian-8 has 2.19
. So, if you want to have to support both distros till Ubuntu-14.04 and Debian-8, you should build the package on a distros with glibc
version 2.19
. This way, the package will run on all newer versions of the target system as well. For this article, we will use Ubuntu-14.04 as the base image.
Select the base image based on the glibc
version you want to support. For this article, we will use Ubuntu-14.04 as the base image.
Now, install the necessary system dependencies to build the Python application. This includes libraries like zlib
, libssl
, libffi
, etc.
In the next step, we will install OpenSSL 1.1.1. This is required by Python 3.9.20.
Next, we will install Python 3.9.20. This is the version we will use to build the Python application.
Next, we will install PyInstaller. You can specify the version of PyInstaller you want to install using the PYINSTALLER_VERSION
argument.
Here, you can see that we are installing the necessary system dependencies, OpenSSL 1.1.1, and Python 3.9.20. We are also installing PyInstaller version 6.10.0. You can change the PyInstaller version by setting the PYINSTALLER_VERSION
argument.
For OpenSSL, we selected version 1.1.1 because it's the minimum version required by Python (https://community.opalstack.com/d/1016-failed-to-install-python-3106-with-user-installed-openssl/5). But, please not that OpenSSL version 3.x.x might not work with Python 3.9.20. So, it's better to stick with OpenSSL 1.1.1. If OpenSSL is not installed properly, you will face issues while working with Python especially in the older Ubuntu version.
Also --enable-shared
is used to build Python as a shared library. This is required by PyInstaller to build the package. This option is only required if you are building Python from source.
Now build the Docker image with proper args if you want to change the PyInstaller version.
Packaging Python Application
Now, it is time to package the Python application using PyInstaller. For that create a new Docker container from the base image and mount the application directory to the container.
NOTE: Replace $(pwd)/app
with the path to your Python application directory.
Now, inside the container, navigate to the application directory and install the application dependencies using pip
. The command might vary depending on the application and its dependencies.
After that run the following command to package the application using PyInstaller.
Here, bin/app.py
is the entry point of the application. Replace it with the actual entry point of your application.
Besides, if your package is residing in a different directory, you can use the --paths
option to specify the path to the package. This is required if the package is not in the same directory as the entry point. Also, you can use the --add-data
option to include additional files in the package. This is useful if the application requires additional files to run.
Now, the package will be created in the dist
directory inside the application directory. You can find the package there. You can distribute this package and run it on any Linux system with glibc
version greater than or equal to 2.19
.
Testing
To test the package on different Linux distributions, you can create a new Docker container from the base image and copy the package to the container.
Now, inside the container, navigate to the dist
directory and run the package.
This will run the package on the Debian 9 system. You can repeat the same process for other Linux distributions as well.
Best Practices and Production Considerations
When using PyInstaller to package your Python application, consider the following practices and production considerations:
- Version Control: Ensure that you version control your application code and the PyInstaller configuration. This will help you track changes and revert to previous versions if needed.
- Dependency Management: Keep track of the dependencies of your application and ensure that they are up to date. This will help you avoid compatibility issues and security vulnerabilities.
- Automated Builds: Consider setting up automated builds for your application using a CI/CD pipeline. This will help you automate the packaging process and ensure that the package is always up to date.
- Testing: Test the packaged application on different Linux distributions to ensure compatibility. This will help you identify any issues early and ensure that the package runs smoothly on all target systems.
Troubleshooting Common Issues
Here are some common issues you might encounter while packaging your Python application using PyInstaller, and how to troubleshoot them:
- Missing System Dependencies: PyInstaller should handle the dependencies. But, if your application requires specific system-level dependencies, you can use the
--add-binary
option in PyInstaller to include system libraries in the package. - File Not Found Error: Ensure all required data files are included with the
--add-data
option in PyInstaller. This will ensure that the application can find the required files at runtime. - Segmentation Fault: If you encounter a segmentation fault while running the packaged application, it might be due to a compatibility issue with the target system. Ensure that the package is built on a system with the same architecture and
glibc
version as the target system. Using a older base image for build might help in this case. - SSL Errors: If you encounter SSL errors while running the packaged application, ensure that correct OpenSSL version is installed correctly on the target system.
Conclusion
In this article, we discussed how to package a Python application using PyInstaller for Linux. We also discussed the limitations of PyInstaller and how to mitigate them. We created a base Docker image with the necessary dependencies to build the Python application and packaged the application using PyInstaller. Besides, we got the point that the package will run on any Linux system with glibc
version greater than or equal to 2.19
which is the version used to build the package (base image).