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.
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:
glibc
, musl
(for Alpine), etc. to be present on the target system.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.To follow all the steps in this article, you need to have the following prerequisites:
x86_64
architecture is recommended as PyInstaller does not support cross-compilation for other architectures.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.
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
.
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.
When using PyInstaller to package your Python application, consider the following practices and production considerations:
Here are some common issues you might encounter while packaging your Python application using PyInstaller, and how to troubleshoot them:
--add-binary
option in PyInstaller to include system libraries in the package.--add-data
option in PyInstaller. This will ensure that the application can find the required files at runtime.glibc
version as the target system. Using a older base image for build might help in this case.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).