Build standalone executable with PyApp
PyApp is a tool for distributing Python applications to end users who have no Python experience. In this topic, we are going to build a standalone executable of Mahoraga with PyApp, utilizing a running Mahoraga instance. The Mahoraga executable can be copied to another machine, and will help you set up Python development environment efficiently.
Note
Mahoraga serves on http://127.0.0.1:3450
by default. Replace it with the actual URL exposed to your clients.
Prerequisites
- A running Mahoraga server which we have set up in the previous tutorial.
-
uv and Pixi should be available in your
PATH
and should have been configured according to the tutorial. If you have only one of them, fetch the other from Mahoraga now:uv run get_pixi.py
get_pixi.py
# /// script # dependencies = [ # "py-rattler >=0.9.0", # ] # requires-python = ">=3.8" # /// import asyncio import shutil import os import rattler.networking async def main(): client = rattler.Client([ rattler.networking.MirrorMiddleware({ "https://conda.anaconda.org/": ["http://127.0.0.1:3450/conda/"], }), ]) records = await rattler.solve( channels=["conda-forge"], specs=["pixi >=0.43.1"], gateway=rattler.Gateway( default_config=rattler.SourceConfig(sharded_enabled=True), client=client, ), ) target_prefix = os.path.expanduser("~/.pixi") await rattler.install(records, target_prefix, client=client) if pixi := shutil.which("pixi", path=os.path.join(target_prefix, "bin")): print(os.path.normcase(pixi)) else: raise FileNotFoundError("pixi") if __name__ == "__main__": asyncio.run(main())
pixi global install uv
Note
Pixi exposes
uv
as a trampoline. Useuv run which uv
to retrieve the actual path on Unix. -
Xcode/MSVC toolchain if you are using macOS/Windows. If you don't need the Xcode/VS IDE, just install Xcode Command Line Tools or Microsoft C++ Build Tools.
- Other dependencies (for example Rust) are managed by Pixi, so they don't require explicit installation here.
Build steps
PyApp doesn't recognize uv packages from PyPI or Anaconda, as a result we have to manually link the uv executable to PyApp cache directory:
export PYAPP_UV_VERSION="$(uv -V | awk '{ print $2 }')"
mkdir -p ~/".cache/pyapp/uv/$PYAPP_UV_VERSION"
ln -fnsT "$(uv run which uv)" ~/".cache/pyapp/uv/$PYAPP_UV_VERSION/uv"
export PYAPP_UV_VERSION="$(uv -V | awk '{ print $2 }')"
mkdir -p ~/"Library/Caches/pyapp/uv/$PYAPP_UV_VERSION"
ln -fns "$(uv run which uv)" ~/"Library/Caches/pyapp/uv/$PYAPP_UV_VERSION/uv"
export PYAPP_UV_VERSION="$(uv -V | awk '{ print $2 }')"
mkdir -p ~/"Library/Caches/pyapp/uv/$PYAPP_UV_VERSION"
ln -fns "$(uv run which uv)" ~/"Library/Caches/pyapp/uv/$PYAPP_UV_VERSION/uv"
$Env:PYAPP_UV_VERSION = $(uv -V).Substring(3)
mkdir -ErrorAction Ignore `
-Path $Env:LOCALAPPDATA\pyapp\cache\uv\$Env:PYAPP_UV_VERSION
New-Item -Type SymbolicLink `
-Value $(Get-Command uv.exe).Source `
-Path $Env:LOCALAPPDATA\pyapp\cache\uv\$Env:PYAPP_UV_VERSION\uv.exe
PyApp itself cannot pack a Python environment, but we can create a Python environment by executing an online bootstrapper.
Make an empty directory, cd
to it, and then build the online version of PyApp:
export PYAPP_PROJECT_NAME=mahoraga
export PYAPP_PROJECT_VERSION=0.2.1
export PYAPP_DISTRIBUTION_SOURCE=http://127.0.0.1:3450/python-build-standalone/20250604/cpython-3.13.4+20250604-x86_64_v3-unknown-linux-gnu-install_only_stripped.tar.gz
export PYAPP_FULL_ISOLATION=1
export PYAPP_UV_ENABLED=1
export PYAPP_UV_VERSION="$(uv -V | awk '{ print $2 }')"
pixi exec -s gcc_linux-64 -s rust cargo install --no-track --root . pyapp
PYAPP_INSTALL_DIR_MAHORAGA=temp UV_PYTHON=temp/python/bin/python3 bin/pyapp
export PYAPP_PROJECT_NAME=mahoraga
export PYAPP_PROJECT_VERSION=0.2.1
export PYAPP_DISTRIBUTION_SOURCE=http://127.0.0.1:3450/python-build-standalone/20250604/cpython-3.13.4+20250604-aarch64-apple-darwin-install_only_stripped.tar.gz
export PYAPP_FULL_ISOLATION=1
export PYAPP_UV_ENABLED=1
export PYAPP_UV_VERSION="$(uv -V | awk '{ print $2 }')"
pixi exec -s rust cargo install --no-track --root . pyapp
PYAPP_INSTALL_DIR_MAHORAGA=temp UV_PYTHON=temp/python/bin/python3 bin/pyapp
export PYAPP_PROJECT_NAME=mahoraga
export PYAPP_PROJECT_VERSION=0.2.1
export PYAPP_DISTRIBUTION_SOURCE=http://127.0.0.1:3450/python-build-standalone/20250604/cpython-3.13.4+20250604-x86_64-apple-darwin-install_only_stripped.tar.gz
export PYAPP_FULL_ISOLATION=1
export PYAPP_UV_ENABLED=1
export PYAPP_UV_VERSION="$(uv -V | awk '{ print $2 }')"
pixi exec -s rust cargo install --no-track --root . pyapp
PYAPP_INSTALL_DIR_MAHORAGA=temp UV_PYTHON=temp/python/bin/python3 bin/pyapp
$Env:PYAPP_PROJECT_NAME = "mahoraga"
$Env:PYAPP_PROJECT_VERSION = "0.2.1"
$Env:PYAPP_DISTRIBUTION_SOURCE = "http://127.0.0.1:3450/python/3.13.4/python-3.13.4-embed-amd64.zip"
$Env:PYAPP_FULL_ISOLATION = "1"
$Env:PYAPP_UV_ENABLED = "1"
$Env:PYAPP_UV_VERSION = $(uv -V).Substring(3)
pixi exec -s rust cargo install --no-track --root . pyapp
$Env:PYAPP_INSTALL_DIR_MAHORAGA = "temp"
bin/pyapp
pyapp
will fail to run since we didn't set PYAPP_DISTRIBUTION_PATH_PREFIX
on Unix and didn't remove python313._pth
on Windows. That doesn't matter since we have already get the Python environment. Pack it with maximum compression rate:
pixi exec -s tar -s zstd tar -cf temp.tar.zst -C temp/python \
--use-compress-program "zstd --ultra -22" .
pixi exec -s zstd tar -cf temp.tar.zst -C temp/python \
--use-compress-program "zstd --ultra -22" .
pixi exec -s zstd tar -cf temp.tar.zst -C temp/python \
--use-compress-program "zstd --ultra -22" .
rm temp/python313._pth
pixi exec -s zstd tar -cf temp.tar.zst -C temp -I "zstd --ultra -22" .
Pass the tarball to PyApp again, and we will finally get the desired offline executable:
export PYAPP_PROJECT_NAME=mahoraga
export PYAPP_PROJECT_VERSION=0.2.1
export -n PYAPP_DISTRIBUTION_SOURCE
export PYAPP_DISTRIBUTION_PYTHON_PATH=bin/python3
export PYAPP_DISTRIBUTION_PATH=~+/temp.tar.zst
export PYAPP_FULL_ISOLATION=1
export PYAPP_SKIP_INSTALL=1
pixi exec -s gcc_linux-64 -s rust cargo install --no-track --root offline pyapp
mv offline/bin/pyapp mahoraga
export PYAPP_PROJECT_NAME=mahoraga
export PYAPP_PROJECT_VERSION=0.2.1
unset PYAPP_DISTRIBUTION_SOURCE
export PYAPP_DISTRIBUTION_PYTHON_PATH=bin/python3
export PYAPP_DISTRIBUTION_PATH=~+/temp.tar.zst
export PYAPP_FULL_ISOLATION=1
export PYAPP_SKIP_INSTALL=1
pixi exec -s rust cargo install --no-track --root offline pyapp
mv offline/bin/pyapp mahoraga
export PYAPP_PROJECT_NAME=mahoraga
export PYAPP_PROJECT_VERSION=0.2.1
unset PYAPP_DISTRIBUTION_SOURCE
export PYAPP_DISTRIBUTION_PYTHON_PATH=bin/python3
export PYAPP_DISTRIBUTION_PATH=~+/temp.tar.zst
export PYAPP_FULL_ISOLATION=1
export PYAPP_SKIP_INSTALL=1
pixi exec -s rust cargo install --no-track --root offline pyapp
mv offline/bin/pyapp mahoraga
$Env:PYAPP_PROJECT_NAME = "mahoraga"
$Env:PYAPP_PROJECT_VERSION = "0.2.1"
$Env:PYAPP_DISTRIBUTION_SOURCE = ""
$Env:PYAPP_DISTRIBUTION_PYTHON_PATH = "python.exe"
$Env:PYAPP_DISTRIBUTION_PATH = Convert-Path temp.tar.zst
$Env:PYAPP_FULL_ISOLATION = "1"
$Env:PYAPP_SKIP_INSTALL = "1"
pixi exec -s rust cargo install --no-track --root offline pyapp
mv offline/bin/pyapp.exe mahoraga.exe
That's it. You can run this mahoraga
on either the same machine or another one, and it will work as expected.