Python EXE Packaging: A Beginner’s Guide
If you have ever asked, “How do I give this Python app to someone who does not have Python installed?” or “How do I stop customers from reading my source code?”, you are really asking the same question: how do I package Python code as an executable?
The short answer: compile your Python into a native binary. Py2Native makes that practical with a zero-config workflow. You write plain Python, run one uv run py2native command, and get a compiled executable or shared library. This guide explains what Python EXE packaging is, why it matters, how Py2Native works under the hood, and how to do it yourself.
What Is Python EXE Packaging?
Python EXE packaging means converting Python scripts into standalone executable programs that run on a target machine without requiring a separate Python interpreter.
On Windows, people often mean a .exe file. On Linux and macOS, the same idea applies: you produce a native ELF or Mach-O binary. The important part is that the end user does not need to install Python, create a virtual environment, or manage packages just to run your software.
Py2Native approaches this as a compiler, not just a bundler. It takes your custom Python code and compiles it into native machine code. That is different from tools that simply freeze a Python interpreter and zip your .py files next to it.
Py2Native supports two main output modes:
- Executable mode: produces a standalone binary that runs your main module directly. This is the right choice for CLI tools, desktop utilities, and end-user applications.
- Library mode: produces a shared library such as
.pyd,.so, or.dylib. This is useful when you want to distribute a compiled package that users can import from their own Python projects.
Both modes protect the source you wrote by shipping compiled code instead of readable .py files.
Why Package Python as an EXE?
There are four practical reasons to compile Python instead of shipping source.
1. Protect proprietary code
Plain .py files are readable. Compiled native code is much harder to reverse engineer. Py2Native does not make your code mathematically impossible to recover, but it raises the barrier from “open the file in a text editor” to serious binary analysis.
2. Simplify distribution
With a native executable, users get a program they can run directly. They do not need to install Python, create a virtual environment, or resolve package versions.
3. Avoid environment issues
Compiled binaries reduce the classic “works on my machine” problem. There is no system Python mismatch, no missing package at runtime, and no accidental dependency conflict.
4. Improve startup and runtime behavior
Native code can start faster than interpreted bytecode because it skips some of Python’s runtime dispatch overhead.
A common misconception is that packaging as an EXE makes code 100% secure. It does not. A determined attacker with reverse-engineering skills can still inspect any binary. The goal is to make casual copying, tampering, and source theft significantly harder.
How Py2Native Works Under the Hood
Py2Native uses Cython under the hood but hides it completely. You do not write .pyx files by hand, run cythonize, configure C compiler flags, or link against CPython yourself.
The high-level pipeline looks like this:
- Py2Native globs your source files under the base directory.
- It generates a bootstrap
.pyxfrom internal templates. - Cython transpiles the Python sources to C.
- A platform compiler such as MSVC, GCC, or Clang compiles the C into native machine code.
- The build system links the result into an executable or shared library.
Third-party Python libraries are left unmodified. They remain as Python source and are embedded using uv, so they work as-is with minimal or no configuration. Your custom code is what gets compiled.
The hard way would be writing C extensions or manually maintaining .pyx and .pxd files. Py2Native’s way is one command.
The optional Pro plugin adds JWT license verification. It bakes the signature verification logic and your public key into the executable, so license checks run in compiled code without requiring third-party verification libraries at runtime.
Step-by-Step: Packaging Your First Python EXE with Py2Native
Prerequisites
You need:
- CPython 3.11 through 3.15, including free-threaded 3.14t and 3.15t
uvinstalled- A platform C compiler or linker: MSVC on Windows, GCC on Linux, or Clang on macOS
- x86-64 or ARM64 CPU
- Internet access so
uvcan download Python distributions and dependencies
All Py2Native commands in this guide use uv run py2native. You do not need pip.
Compile a single script
Start with a simple Python file:
# main.py
def main():
print("Hello from a native Py2Native binary!")
if __name__ == "__main__":
main()
Compile it:
uv run py2native build main.py
Py2Native produces a native executable from main.py. You can run that binary directly on the target platform without Python installed.
Compile multiple files
If your project has several modules, pass them all:
uv run py2native build main.py app/*.py
Py2Native expands glob patterns and compiles the matching sources.
Create a library instead of an executable
Use the --library flag to produce a shared library:
uv run py2native build --library main.py mypkg/*.py
Library mode creates a shared object that can be imported as a Python package. This is useful when your users already have Python and you want to protect your package without forcing a standalone binary on them.
Embed a Python environment for deployment
The --embed option creates a uv-managed deployment directory:
uv run py2native build main.py *.py --embed ./deploy
The resulting directory contains the compiled binary and the Python dependencies it needs.
Build a wheel for distribution
Use --wheel to produce a PEP 427 wheel containing the compiled shared library:
uv run py2native build --library main.py mypkg/*.py --wheel ./dist
The wheel includes a compiled .pyd or .so plus the __init__.py and __main__.py files that bridge Python’s import system to the native library.
Adding License Verification with Py2Native Pro
If you sell proprietary software, you may want to control who can run it. Py2Native Pro adds JWT-based license verification with ES256 signatures.
Start by generating an elliptic curve keypair:
uv run py2native keygen private.pem public.pem
Sign a license payload with the private key:
uv run py2native sign --private private.pem '{"sub":"customer-123","iss":"RSJ Software GmbH","aud":"TimestampGIT","exp":1798761600}' license.dat
Inspect the license claims:
uv run py2native show --public public.pem license.dat
To verify licenses inside your application, add a .pxd file to your project:
# license_bridge.pxd
from _p2n_bootstrap cimport _runtime_verify_es256_jwt
cdef _runtime_verify_es256_jwt(token, expected_iss=*, expected_aud=*)
Then call the verification logic from your Python code:
# main.py
from pathlib import Path
licenseString = Path("license.dat").read_text().strip()
license = _runtime_verify_es256_jwt(
licenseString,
expected_iss="RSJ Software GmbH",
expected_aud="TimestampGIT",
)
print("License verified")
Build with the Pro license and public key:
uv run py2native build main.py app/*.py --license license.dat --public public.pem
Only the public key is stored in the executable. The elliptic key verification is handled with compiled code in the Pro plugin, so there are no third-party verification libraries required at runtime.
For a deeper walkthrough, see Secure JWT License Verification with Py2Native Pro.
Common Pitfalls and How to Avoid Them
Identical module names
Py2Native does not support modules with identical names across source files. Keep module names unique within your project.
Monkey-patching is still possible
Third-party libraries remain as Python source, so an end user can still monkey-patch them. Py2Native protects your custom code, but it does not prevent all modifications to third-party packages.
LGPL libraries remain replaceable
If you use LGPL libraries, end users retain the right to replace those libraries. Be aware of your license obligations when distributing compiled software.
__init__.py is skipped
Py2Native skips __init__.py during compilation because it is a namespace marker, not compilable code. Do not expect logic placed there to be compiled.
Do not include __main__.py in library mode
In library mode, the wheel builder generates __main__.py automatically. Exclude it from your source list.
Cross-platform differences
Native output is platform-specific. Build on each target platform or use an appropriate cross-compilation setup. Py2Native’s platform plugins apply the correct compiler and linker flags for Windows, Linux, and macOS.
FAQ
Does Py2Native work with all Python libraries?
Py2Native compiles your custom Python code into native machine code. Third-party libraries are left unmodified and embedded using uv, so they work as-is with minimal or no configuration. Some libraries with C extensions may require platform-specific handling, but Py2Native’s platform plugins handle most cases automatically.
Is the compiled executable completely secure?
No method can guarantee 100% security, but Py2Native significantly raises the bar by compiling your Python code to native machine code. It is much harder to reverse engineer than plain .py files. The Pro plugin adds JWT license verification with the public key and verification logic baked into the executable.
Can I package my Python code for multiple platforms?
Yes. Py2Native supports Windows, Linux, and macOS on x86-64 and ARM64. Because the output is platform-specific, build on each target platform or use cross-compilation if available. The platform plugins automatically apply the correct compiler and linker flags.
What is the difference between an executable and a library build?
An executable build creates a standalone binary that runs your main module directly. A library build creates a shared object such as .pyd, .so, or .dylib that can be imported as a Python package. Library mode is useful when you want to distribute a compiled package that users can import while still protecting your source code.
Conclusion
Python EXE packaging does not have to mean shipping .py files or hand-tuning C extensions. Py2Native compiles your plain Python code into native machine code with a single uv run py2native build command, while leaving third-party libraries untouched and working as-is.
If you are shipping proprietary Python software and want a practical protection story, start with a small script and see how far one command gets you. Then add license verification through the Pro plugin when you need to control who runs your product.
The fastest way to answer the Python EXE packaging question is to try Py2Native yourself.
Related posts
- Automate Python Compilation with Py2Native
- Secure JWT License Verification with Py2Native Pro
- Py2Native vs Nuitka: Which Python Compiler Protects Your Code Better?