Written in Rust, the PyApp utility wraps up Python programs into self-contained click-to-run executables. It might be the easiest Python packager yet.
Every developer knows how hard it is to redistribute a Python program as a self-contained, click-and-run package. There are third-party solutions, but they all have drawbacks. PyInstaller, the oldest and best-known tool for this job, is crotchety to work with and requires a fair amount of trial-and-error to get a working redistributable. Nuitka, a more recent project, compiles Python programs to redistributable binaries, but the resulting artifacts can be massive and take a long time to produce.
A newer project, PyApp, takes an entirely different approach. Itโs a Rust program you compile from source, along with information about the Python project you want to distribute. The result is a self-contained binary that, when run, unpacks your project into a directory and executes it from there. The end user doesnโt need to have Python on their system to use it.
Setting up PyApp
Unlike other Python distribution solutions, PyApp is not a Python library like PyInstaller. Itโs also not a standalone program that takes in your program and generates an artifact from it. Instead, you create a custom build of PyApp for each Python program you want to distribute.
Youโll need to take care of a few prerequisites before using PyApp to deploy Python programs:
- PyAppโs source: Make a clone of PyAppโs source and put it in a directory by itself, separate from any other projects.
- The Rust compiler and any other needed infrastructure: If youโre unfamiliar with Rust or its tooling, youโll need to understand at least enough of it to compile a Rust program from source. Check out my tutorial for getting started with Rust to see what you need.
- Your Python program, packaged as a wheel: The โwheel,โ or .
whlfile, is the binary format used to package Python programs along with any platform-specific components (such as precompiled libraries). If you donโt already have a wheel for the Python program you want to repackage, youโll need to generate one. My video tutorial for creating Python wheels steps you through that process. You can also use wheels hosted on PyPI.
PyApp uses environment variables during the build process to figure out what Python project you want to compile into it and how to support it. The following variables are the ones most commonly used:
- PYAPP_PROJECT_NAME: Used to define the name of the Python project youโll be bundling. If you used
pyproject.tomlto define your project, it should match theproject.nameattribute. This can also be the name of a project on PyPI; if not, you will want to define the path to the.whlfile to use. - PYAPP_PROJECT_VERSION: Use this to configure a specific version of the project if needed.
- PYAPP_PROJECT_PATH: The path (relative or absolute) to the
.whlfile youโll use for your project. Omit this if youโre just installing a.whlfrom PyPI. - PYAPP_EXEC_MODULE: Many Python packages run automatically in some form when run as a module. This variable lets you declare which module to use, so if your program runs with
thisprogram, youโd set this variable to:python -m thisprogram. - PYAPP_EXEC_SPEC: For programs that have an entry-point script, you can specify it here. This matches the syntax in the
project.scriptssection ofpyproject.toml. For instance,pyprogram.cmd:mainwould import the modulepyprogram.cmdfrom your Python programโs modules, then execute the functionmain()from it. - PYAPP_EXEC_SCRIPT: This variable lets you supply a path to an arbitrary Python script, which is then embedded in the binary and executed at startup.
- PYAPP_DISTRIBUTION_EMBED: Normally, when you create a PyApp binary, it downloads the needed Python distribution to run your program from the Internet when itโs first executed. If you set this variable to
1, PyApp will pre-package the needed Python distribution in the generated binary. The result is a larger binary, but one that doesnโt need to download anything; it can just unpack itself and go.
Many other options are available, but these should be enough for most projects you want to build.
To make things easy on yourself, you may want to create a shell script for each project that sets up these environment variables and runs the compilation process.
Building the PyApp binary
Once youโve set the environment variables, go to the root directory of PyAppโs source, and build PyApp using the Rust compiler with the command:
cargo build --release
This might take several minutes, as PyApp has many dependencies (363 as of this writing). Future compilation passes will take much less time, though, once Rust obtains and caches everything.
Note that while itโs possible to cross-compile for other platforms, it is not recommended or supported.
Once the compiling is done, the resulting binary will be in the target/release subdirectory of the PyApp project directory, as pyapp.exe. You can rename the resulting file anything you want, as long as itโs still an executable.
Running the PyApp binary
To test the binary, just run it from a console. If all is well, you should see prompts in the console as PyApp unpacks itself and prepares to run. If you see any failures in the console, take note of them and edit your environment variables; chances are you didnโt properly set up the entry point or startup script for the project.
When the PyApp binary runs for the first time, itโll extract itself into a directory thatโs usually a subdirectory of the user profile. On future runs, itโll use that already-unpacked copy and so will launch much faster.
If you want to control where the binary unpacks itself, you can set an environment variable to control where the program writes and looks for its unpacked contents when itโs run:
PYAPP_INSTALL_DIR_<project_name> = "path/to/directory"
Note that <project_name> is an uppercased version of the PYAPP_PROJECT_NAME variable. So, for the program conwaylife, weโd use the variable name PYAPP_INSTALL_DIR_CONWAYLIFE. The directory can be a full path or a relative one, so you could use a directory like ./app to indicate the application should be unpacked into the subdirectory app of the current working directory.
Also note that this setting is not persistent. If you donโt have this exact environment variable set when you run the program, itโll default to the user-profile directory.
PyApp options
The deployed PyApp executable comes with a couple of convenient commands built into it:
- pyapp self remove: Removes the unpacked app from the directory it was unpacked into.
- pyapp self restore: Removes and reinstalls the app.
Note again that if you used the PYAPP_INSTALL_DIR_ variable to set where the project lives, you must also have it set when running these commands! Itโs also important to note that PyApp-packaged apps can generate false positives with antivirus solutions on Microsoft Windows, because the resulting executables arenโt code-signed by default.


