Axe
Axe ships your Python app as a fully self-contained single-file binary — for every major platform, from any machine, with zero extra toolchain.
- App developers need no Go, no cross-compilers, no Docker. Axe's wheel
ships precompiled runtime stubs;
axe buildglues your app onto them. - App users need no Python, no uv, and no network. The binary embeds uv, CPython, and every dependency wheel; the first run unpacks them into a cached environment and every run after is instant. Corporate proxy, air-gap, TLS-intercepting middlebox — none of it matters.
Quick start
Your project needs to be an installable package with a console script — a
project created with uv init --package is already set up correctly (a bare
uv init app is not packaged; axe build will tell you what to add):
$ uv init --package mycli
$ cd mycli
$ uv add --dev axe
$ uv run axe build --all-platforms
built dist/bin/mycli-0.1.0-linux-amd64 (52 MB, python 3.12.13, 0 dependency wheels)
built dist/bin/mycli-0.1.0-linux-arm64 (50 MB, python 3.12.13, 0 dependency wheels)
built dist/bin/mycli-0.1.0-darwin-amd64 (48 MB, python 3.12.13, 0 dependency wheels)
built dist/bin/mycli-0.1.0-darwin-arm64 (47 MB, python 3.12.13, 0 dependency wheels)
built dist/bin/mycli-0.1.0-windows-amd64.exe (55 MB, python 3.12.13, 0 dependency wheels)
Hand the binary to anyone — nothing to install, nothing to download:
$ ./dist/bin/mycli-0.1.0-darwin-arm64
[axe] bootstrapping mycli 0.1.0...
Hello from mycli!
$ ./dist/bin/mycli-0.1.0-darwin-arm64 # instant from now on
Hello from mycli!
A fuller example lives in examples/cowsay.
Documentation
Browsable at soasme.github.io/axe, or right here in the repo:
- Getting started — from zero to a shippable binary
- Tutorial: package a CLI as a single binary
- Tutorial: release binaries from GitHub Actions
- CLI reference
- Configuration reference — the
[tool.axe]table - Runtime reference —
selfcommands, environment variables, install locations - Runtime internals — how the bootstrap works under the hood
How it works
axe build assembles, per target platform, a payload containing everything a
machine needs to run your app:
- your project's wheel (built with uv),
- every dependency wheel, resolved for that platform (
uv pip compile --python-platform+pip download), - a pinned CPython from python-build-standalone,
- a pinned uv,
and appends it to a small precompiled Go runtime stub. All artifacts are fetched once on the build machine (checksum-verified, cached), so builds after the first are seconds.
At first run the stub unpacks the embedded Python and uv, creates a venv, and
installs the embedded wheels with --offline --no-index — the runtime
contains no network code at all. Installations live in a per-app directory
keyed by payload fingerprint, so a new binary version gets a fresh
environment automatically. Subsequent runs are a single existence check, then
execvp straight into your app.
The full runtime flow — and how it maps onto pyapp's — is documented in
docs/runtime.md. Apps run in Python's isolated mode, so
the user's PYTHONPATH, user site-packages, and uv/pip configuration never
leak in.
Because dependencies are shipped as wheels, every dependency must publish a wheel for each target platform (pure-Python wheels cover all of them). Binaries weigh roughly 45–60 MB — that's a complete CPython plus uv; disk is cheap, broken installs are not.
Configuration
Everything is optional; defaults come from [project]:
[tool.axe]
entrypoint = "mycli" # default: the sole [project.scripts] entry
# also accepts "-m pkg" or "pkg.mod:func"
python = "3.12" # "3.X" picks the newest 3.X.*; "3.X.Y" pins
# default: lower bound of requires-python
uv-version = "0.10.6" # uv embedded into the binary
python-release = "20260623" # python-build-standalone release tag
uv-releases-url = "https://mirror.corp/uv" # download uv from a mirror
python-build-standalone-releases-url = "https://mirror.corp/pbs" # ...and CPython
expose = ["metadata"] # extra `self` commands: python, python-path,
# cache, metadata — or "all"
self-command-group = true # false: don't reserve `self` at all
CLI
$ axe build [PROJECT] [-o DIR] [-p OS/ARCH]... [--all-platforms] [-q | -v]
$ axe platforms
-q/--quiet prints nothing but errors; -v/--verbose streams the output
of the underlying tools (useful behind corporate proxies — if downloads fail
or stall there, set UV_NATIVE_TLS=1 so uv uses the system trust store).
axe build honors uv's UV_* environment variables — e.g. UV_INDEX_URL
to resolve and download dependencies from a private index; see the
configuration reference for details.
Targets: linux/amd64, linux/arm64, darwin/amd64, darwin/arm64,
windows/amd64.
Runtime management commands
Built binaries reserve one command group, self; everything else goes to
your app:
$ myapp self remove # wipe the installation
$ myapp self restore # wipe and reinstall
$ myapp self update # reinstall from the embedded payload
If your CLI needs self for itself, set self-command-group = false in
[tool.axe] and the binary reserves nothing — self reaches your app like
any other argument.
AXE=1 is set in your app's environment so it can detect axe installs, and
AXE_DEBUG=1 makes the stub verbose.
Developing axe itself
This is the one place a Go toolchain is required:
$ python scripts/build_stubs.py # cross-compile runtime stubs (needs go)
$ uv run pytest # unit + end-to-end tests (offline-verified)
$ cd runtime && go test ./... # runtime unit tests
$ uv build # the wheel, stubs included
Lint and formatting are enforced by pre-commit
(ruff check + format); install the hooks once with uvx pre-commit install.
CI runs the same hooks plus both test suites on every pull request.
Runtime behavior is documented in docs/runtime.md.
Metadata
Release files for axe 0.5.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| axe-0.5.1.tar.gz | 5.4 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| axe-0.5.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 10.8 MB
Release files / axe-0.5.1.tar.gz
| Download URL | axe-0.5.1.tar.gz |
|---|---|
| Size | 5.4 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e0ace2f0aa94a4cd960fee9034b698cd6ee036ad49eb82fd6f7a460bd0c5a840
|
|
BLAKE2b-256 checksum How to use checksums |
7a5faa859b769912171ec46ead32797b5f45469979826151d34b16cbed9c9e38
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 14, 2026.
Transparency logRelease files / axe-0.5.1-py3-none-any.whl
| Download URL | axe-0.5.1-py3-none-any.whl |
|---|---|
| Size | 5.4 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ab4ff60b7a866da20d0648151b20844f2d500e501489a132218f0c41f73e6881
|
|
BLAKE2b-256 checksum How to use checksums |
081aba43b56f1acabac070574122d68f04367216ef95cccd03d3dc407b8828e0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 14, 2026.
Transparency log