Skip to content

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. Use uv 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"
PowerShell (Run as Administrator)
$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
PowerShell
$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" .
PowerShell
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
PowerShell
$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.