Bundle up your Python app with everything it needs. One command turns a locked project into a
single .pyz that runs on plain Python: no install step, no network, native extensions included.
And it tells you before you ship what won't survive the trip.
Dead simple to use, but also powerful and fast: esbuild's experience, for Python.
Status: alpha (0.1.0). Builds for this machine or another platform (Linux, macOS, Windows; x86_64 and arm64), passes the gauntlet on all four in CI, and the own test suites of click, packaging, markupsafe and itsdangerous give the same results from a bundle as from a venv (nightly). Try it without installing via
uvx bundleup build, or install withuv tool install bundleup/pip install bundleup.
bundleup build # the project in this directory -> dist/<name>.pyz
bundleup build path/to/script.py # a PEP 723 script with inline dependencies
bundleup build --python 3.9 # build with Python 3.9 (pure Python: runs on 3.9 and newer)
bundleup build --python 3.11 --python-platform linux # build on a Mac for Linux x86_64
bundleup build --python-platform linux --python-platform macos --python-platform windows # one file for all three
bundleup build --json # the result as JSON on stdout, for scripts and agents
bundleup check # what won't survive bundling, and each package's size
bundleup check --python-platform linux --python-platform windows # also check Windows wheels
bundleup check --matrix # which OS, CPU and Python versions the lock's wheels cover
bundleup build --smoke # then run it once: fresh home folder, no network
bundleup build --max-size 30MB # fail, writing nothing, if the bundle is bigger
bundleup build --format dir # a plain directory, for apps that load packages from one
bundleup build --format zip # a flat .zip of code, packages and bytecode (e.g. serverless)
bundleup build --format py # one plain-text .py file (for small tools: gists, uploads)
bundleup build --split 100MB # no file over 100 MB: a small .pyz and a folder of parts
bundleup build --format exe # experimental: an executable with its own Python, unsigned
bundleup verify dist/app.pyz # bundle and unpacked copy vs its manifest (corruption; not a signature)
bundleup cache clean # remove unpacked bundles not used for 30 days
python dist/<name>.pyz # run it: no install, no networkbundleup reads what you have: pyproject.toml with uv.lock or pylock.toml, a script's
# /// script block, or, without a lock, a pyproject.toml, setup.py or requirements.txt
(resolved at build time, with a warning; recipes). It brings its own
uv for the build; the bundle needs only Python. On first run it
unpacks to a cache (~/Library/Caches/bundleup, ~/.cache/bundleup, or a temp directory if
those aren't writable), so later runs start as fast as an installed virtualenv; unpacking a new version removes older
copies of the same bundle that went unused for 30 days. A pure-Python
bundle runs on every Python version its lock allows (Python 3.10+), and on any OS when the lock
picks the same packages everywhere (on any OS); one with compiled code runs on the version and
platform it was built for. A bundle only sees
its own packages and the standard library; set BUNDLEUP_INHERIT_PATH=1 to also let it use
packages installed on the machine (they come after the bundle's, but a machine package can still
win over a bundled namespace package of the same name: a Python rule, PEP 420). If it's started
with the wrong Python, it runs itself again with a matching one if one is installed; otherwise, and on
the wrong platform, it says so in one sentence. Programs it starts with
its own Python see its packages; any other Python it starts doesn't. bundleup never writes into
your project.
Every build checks the code first: a file of your project that doesn't compile on the target
Python stops the build, and anything that may not work in a bundle (a dependency's file that needs
a newer Python, data files a package expects under sys.prefix) is a warning. bundleup check
runs the same checks without writing a bundle and shows how big each package is; --strict makes
warnings fail too.
Recipes for Claude Skills, AWS Lambda, host applications, CI and HPC: docs/recipes.md.
Python has three ways to hand a program to someone else, and the middle one is missing:
| Ship as | The other machine needs | Problem |
|---|---|---|
| Standalone executable | Nothing | Per-OS builds, code signing, antivirus (bundleup can wrap a bundle as one, unsigned: --format exe) |
Source + pip install / uv sync |
Python, an installer, network | The install step is where things break |
| One file that runs on the user's Python | Python | No easy, reliable tool. That's bundleup |
pex, shiv and zipapps can do parts of this. bundleup's bet is a much better experience on top of the same correct approach: no flags to get it fast, plain-language errors, and a pre-ship check. Measured against pex's fastest configuration: builds 1.1-2.4× faster, warm starts as fast as an installed venv (pex adds 45-80 ms), first runs 2.3-15× faster (findings). See docs/vision.md.
- MISSION.md: goal, scope, what "done" means
- docs/vision.md: where bundleup fits and who it's for
- docs/roadmap.md: where it could go next
- docs/python-primer.md: Python packaging explained for JavaScript developers
- docs/adr/: decisions and why they were made
- docs/findings/2026-10-03-baseline.md: how pex, shiv and zipapps fare today
- gauntlet/: the test projects every bundler is measured against
uv run gauntlet/check_native.py # control group: projects run normally
uv run gauntlet/run_bundlers.py --conditions --out run # existing bundlers, incl. hostile conditions
uv run gauntlet/report.py gauntlet/results/run.json > gauntlet/results/run.md