Python PyOxidizer Distribution — Core Concepts

What PyOxidizer does

PyOxidizer produces standalone executables that contain a fully embedded Python interpreter, the standard library, and your application code — all in a single binary. It differs from tools like PyInstaller in a fundamental way: instead of bundling Python alongside your code, PyOxidizer compiles a Rust program that links against libpython and loads Python modules from data embedded in the binary.

The result is a native executable with no extraction step, no temp directories, and no dependency on the system Python.

How it works

PyOxidizer’s build process:

  1. Builds a custom Python distribution — downloads or compiles a Python interpreter suitable for static linking.
  2. Collects your code — gathers your Python modules and their dependencies.
  3. Embeds everything — Python bytecode, extension modules, and data files are packed into the Rust binary as compiled-in data.
  4. Compiles the Rust wrapper — the Rust program initializes the embedded Python interpreter and runs your entry point.

The configuration lives in a pyoxidizer.bzl file (Starlark syntax, similar to Bazel):

def make_exe():
    dist = default_python_distribution()
    policy = dist.make_python_packaging_policy()
    
    python_config = dist.make_python_interpreter_config()
    python_config.run_module = "myapp"
    
    exe = dist.to_python_executable(
        name="myapp",
        packaging_policy=policy,
        config=python_config,
    )
    
    exe.add_python_resources(exe.pip_install(["."]))
    
    return exe

register_target("exe", make_exe)
resolve_targets()
# Build the executable
pyoxidizer build

# Run it directly
pyoxidizer run

# Output location
ls build/*/release/install/myapp

The oxidized importer

PyOxidizer’s key innovation is its oxidized importer — a custom Python import system written in Rust that replaces the standard filesystem-based importer.

Standard Python import:

import mylib → search sys.path → find mylib.py on disk → read file → compile → execute

Oxidized import:

import mylib → look up in embedded data table → load bytecode from memory → execute

This in-memory import is faster than filesystem-based import because:

  • No filesystem calls (no stat, open, read syscalls).
  • Bytecode is pre-compiled and stored ready to execute.
  • All data is in the process’s memory space from startup.

The speedup is most noticeable in applications that import many modules at startup — a Flask/Django app might start 30-50% faster.

What gets embedded

By default, PyOxidizer embeds:

  • Python interpreter (libpython) — statically linked into the binary.
  • Standard library — as pre-compiled bytecode in the binary’s data section.
  • Your application code — as bytecode.
  • Pure-Python dependencies — as bytecode.
  • C extension modules — as shared libraries bundled in the binary (extracted to memory or a temp path as needed).

Data files (templates, configs, images) can be included too:

exe.add_python_resources(
    exe.pip_install(["."]),
)
# Add extra data files
exe.add_filesystem_relative("data", exe.read_package_root(
    "myapp", ["myapp/templates/**"]
))

When to use PyOxidizer

Good fits:

  • CLI tools where startup speed matters.
  • Internal tooling distributed as a single binary.
  • Embedded Python in larger Rust applications.
  • Security-conscious distribution — no Python files on disk to tamper with.

Less ideal for:

  • Quick prototyping — the build process is heavier than PyInstaller.
  • Applications with many C extensions — some extensions resist static linking.
  • Frequent iteration — build times are longer due to Rust compilation.

PyOxidizer vs PyInstaller

FeaturePyOxidizerPyInstaller
OutputTrue embedded binaryBundled archive + bootloader
Extraction neededNoYes (onefile mode)
Startup speedFast (no extraction)Slow in onefile, fast in onefolder
Build complexityHigher (Starlark config, Rust)Lower (CLI flags)
Import speedFaster (in-memory)Standard
MaturityNewer, less ecosystem coverageMature, well-tested
C extension supportImproving but has gapsComprehensive

Current project status

PyOxidizer’s development slowed after its creator moved to different projects. The tool works well for its supported use cases, but community activity is lower than tools like PyInstaller or Nuitka. Consider this when choosing for a long-term project — evaluate whether the current feature set meets your needs without relying on future development.

Common misconception

“PyOxidizer compiles Python code to native code.”

PyOxidizer does not compile your Python to machine code. Your code still runs as Python bytecode, interpreted by CPython. What changes is how the interpreter and your code are packaged — embedded in a single binary instead of spread across filesystem directories. The performance gains come from faster imports and startup, not from compilation.

One thing to remember: PyOxidizer’s unique value is true embedding — Python becomes part of your binary rather than a dependency, producing the cleanest possible single-file distribution at the cost of a more complex build process.

pythonpyoxidizerdistributionpackagingembedding

See Also

  • Python Appimage Distribution An AppImage is like a portable app on a USB stick — download one file, double-click it, and your Python program runs on any Linux computer without installing anything.
  • Python Briefcase Native Apps Imagine a travel agent who repacks your suitcase for each country's customs — Briefcase converts your Python app into proper native packages for every platform.
  • Python Flatpak Packaging Flatpak wraps your Python app in a safe bubble that works on every Linux system — like a snow globe that keeps your program perfect inside.
  • Python Mypyc Compilation Your type hints are not just for documentation — mypyc turns them into speed boosts by compiling typed Python into fast C extensions.
  • Python Nuitka Compilation What if your Python code could run as fast as a race car instead of a bicycle? Nuitka translates Python into C to make that happen.