{"id":27915501,"url":"https://github.com/jlevy/py-app-standalone","last_synced_at":"2026-03-27T06:22:32.716Z","repository":{"id":287469572,"uuid":"964333133","full_name":"jlevy/py-app-standalone","owner":"jlevy","description":"Standalone, relocatable Python app installs with uv","archived":false,"fork":false,"pushed_at":"2025-04-19T01:24:47.000Z","size":94,"stargazers_count":7,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-19T06:54:54.795Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jlevy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-04-11T03:48:12.000Z","updated_at":"2025-04-19T01:22:07.000Z","dependencies_parsed_at":"2025-04-11T22:39:00.873Z","dependency_job_id":"086f1dd4-a4bc-44e8-9d99-f39ace6152da","html_url":"https://github.com/jlevy/py-app-standalone","commit_stats":null,"previous_names":["jlevy/pip-build-standalone"],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jlevy%2Fpy-app-standalone","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jlevy%2Fpy-app-standalone/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jlevy%2Fpy-app-standalone/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jlevy%2Fpy-app-standalone/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jlevy","download_url":"https://codeload.github.com/jlevy/py-app-standalone/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252718029,"owners_count":21793423,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-05-06T15:55:00.733Z","updated_at":"2025-12-14T05:27:30.675Z","avatar_url":"https://github.com/jlevy.png","language":"Python","funding_links":[],"categories":["Freezers"],"sub_categories":[],"readme":"# py-app-standalone\n\npy-app-standalone builds a standalone, relocatable Python installation with a set of\npackages included. It's like a modern alternative to\n[PyInstaller](https://github.com/pyinstaller/pyinstaller) that leverages the newer uv\necosystem.\n\nIt's a wrapper around [uv](https://github.com/astral-sh/uv) that creates a standalone\nPython installation, runs `uv pip install`, and then makes the required\nplatform-specific changes so you have a fully self-contained install directory\n(packagable and runnable from any directory on a machine of the same platform).\n\n## Background\n\nTypically, Python installations are not relocatable or transferable between machines,\neven if they are on the same platform, because often there are variable, dynamic library\ndependencies oand because scripts and libraries contain absolute file paths (i.e., many\nscripts or libs include absolute paths that reference your home folder or system paths\non your machine).\n\nNow [Gregory Szorc](https://github.com/indygreg)\n[and Astral](https://astral.sh/blog/python-build-standalone) solved a lot of the\nchallenge with\n[standalone Python distributions](https://github.com/astral-sh/python-build-standalone),\nwhich handles static linking and various other issues—see the\n[technical notes](https://gregoryszorc.com/docs/python-build-standalone/main/technotes.html).\nuv also supports [relocatable venvs](https://github.com/astral-sh/uv/pull/5515), so it's\npossible to move a venv.\nBut at least currently, the actual Python installations created by uv can still have\nabsolute paths inside them in the dynamic libraries or scripts, as discussed in\n[this issue](https://github.com/astral-sh/uv/issues/2389).\n\nThis tool is my quick attempt at fixing this, so you basically are using uv but have a\nfully self-contained installation, with your chosen version of Python and any packages\nyou wish.\n\nThe idea is this pre-built binary package can then be used on any machine of a given\nplatform withou any external dependencies, not even Python or uv.\nAnd the the directory is relocatable.\nSo you could for example put it inside a desktop app.\n\nThis should work for any platform.\nYou just need to build on the same platform you want to run on.\n\nWarning: Experimental!\nThis is a new tool. I've used it on macOS and it's very lightly tested on Ubuntu and\nWindows, but obviously there are many possibilities for subtle incompatibilities within\na given platform.\n\n## Alternatives\n\n[PyInstaller](https://github.com/pyinstaller/pyinstaller) is the classic solution for\nthis and has a lot of features beyond this little tool, but is far more complex and does\nnot build on uv.\n\n[shiv](https://github.com/linkedin/shiv) and [pex](https://github.com/pex-tool/pex) are\nmature options that focus on zipping up your app, but not the Python installation.\n\n[PyApp](https://github.com/ofek/pyapp) is a more recent effort on top of uv that creates\na Rust-built standalone binary that downloads/installs Python and dependencies at\nruntime.\n\n## Usage\n\nRequires `uv` to run.\nDo a `uv self update` to make sure you have a recent uv (I'm currently testing on\nv0.6.14).\n\nThen:\n\n```shell\nuvx py-app-standalone --help\nuvx py-app-standalone cowsay  # whatever packages you wish\n```\n\nAs an example, let's create a full standalone Python 3.13 environment with the `cowsay`\npackage.\n\nAfter this is done, the `./py-standalone` directory will work without being tied to a\nspecific machine, your home folder, or any other system-specific paths.\nBinaries can now be put wherever and run:\n\n```log\n$ uvx py-app-standalone cowsay\nCreating a new Python installation at: py-standalone\n\n▶ uv python install --managed-python --install-dir /Users/levy/wrk/github/py-app-standalone/py-standalone 3.13\nInstalled Python 3.13.3 in 2.78s\n + cpython-3.13.3-macos-aarch64-none\n⏱ Call to run took 2.80s\n\nCreating temporary venv (you can ignore this step. It is just a trick to get a uv pyvenv.cfg file)...\n\n▶ uv venv --relocatable --python py-standalone/cpython-3.13.3-macos-aarch64-none py-standalone/bare-venv\nUsing CPython 3.13.3 interpreter at: py-standalone/cpython-3.13.3-macos-aarch64-none/bin/python3\nCreating virtual environment at: py-standalone/bare-venv\nActivate with: source py-standalone/bare-venv/bin/activate\n⏱ Call to run took 628ms\n\nTemporary venv config: py-standalone/cpython-3.13.3-macos-aarch64-none/pyvenv.cfg\nNow installing packages directly into the original installation: py-standalone/cpython-3.13.3-macos-aarch64-none\n\n▶ uv pip install cowsay --python py-standalone/cpython-3.13.3-macos-aarch64-none --break-system-packages\nUsing Python 3.13.3 environment at: py-standalone/cpython-3.13.3-macos-aarch64-none\nResolved 1 package in 39ms\nInstalled 1 package in 7ms\n + cowsay==6.1\n⏱ Call to run took 59.77ms\n\nmacOS: Updating dylib ids to be relocatable...\nFound macos dylib, will update its id to remove any absolute paths: py-standalone/cpython-3.13.3-macos-aarch64-none/lib/libpython3.13.dylib\n\n▶ install_name_tool -id @executable_path/../lib/libpython3.13.dylib py-standalone/cpython-3.13.3-macos-aarch64-none/lib/libpython3.13.dylib\n⏱ Call to run took 38.14ms\n\nMaking sure all the scripts are relocatable...\nInserting relocatable shebangs on scripts in:\n    py-standalone/cpython-3.13.3-macos-aarch64-none/bin/*\nReplaced shebang in: py-standalone/cpython-3.13.3-macos-aarch64-none/bin/cowsay\n...\nReplaced shebang in: py-standalone/cpython-3.13.3-macos-aarch64-none/bin/pydoc3\nReplacing remaining absolute paths...\n\nReplacing all absolute paths in:\n    py-standalone/cpython-3.13.3-macos-aarch64-none/bin/* py-standalone/cpython-3.13.3-macos-aarch64-none/lib/**/*.py:\n    `/Users/levy/wrk/github/py-app-standalone/py-standalone` -\u003e `py-standalone`\nReplaced 27 occurrences in: py-standalone/cpython-3.13.3-macos-aarch64-none/lib/python3.13/_sysconfigdata__darwin_darwin.py\nReplaced 27 total occurrences in 1 files total\nCompiling all python files in: py-standalone...\nSanity checking if any absolute paths remain (including in binary files)...\nGreat! No absolute paths found in the installed files.\n\n✔ Success: Created standalone Python environment for packages ['cowsay'] at: py-standalone\n\n$ ./py-standalone/cpython-3.13.3-macos-aarch64-none/bin/cowsay -t 'im moobile'\n  __________\n| im moobile |\n  ==========\n          \\\n           \\\n             ^__^\n             (oo)\\_______\n             (__)\\       )\\/\\\n                 ||----w |\n                 ||     ||\n\n$ # Now let's confirm it runs in a different location!\n$ mv ./py-standalone /tmp\n\n$ /tmp/py-standalone/cpython-3.13.3-macos-aarch64-none/bin/cowsay -t 'udderly moobile'\n  _______________\n| udderly moobile |\n  ===============\n               \\\n                \\\n                  ^__^\n                  (oo)\\_______\n                  (__)\\       )\\/\\\n                      ||----w |\n                      ||     ||\n\n$\n```\n\n## How it Works\n\nIt creates true (not venv) Python installation with the given packages installed, with\nzero absolute paths encoded in any of the Python scripts or libraries.\n\nIt does this by:\n\n- Installing a new standalone/relocatable Python installation with uv\n\n- Ensuring all scripts in `bin/` have relocatable shebangs (normally they are absolute)\n\n- Cleaning up a few other places source directories are baked into text files (like the\n  sysconfigdata .py file)\n\n- On macOS, fixing the hard-coded absolute path inside the Python .dylib file to be\n  relative using `install_name_tool` and its `@executable_path` var.\n  (On Linux and Windows something like this doesn't seem to be necessary.)\n\nIt seems to work.\nSo *in theory*, the resulting binary folder should be installable as at\nany location on a machine with compatible architecture.\n\n## More Notes\n\n- I've only tested this with PyPI packages but it should work for any package name that\n  works with `uv pip` (so should work with private packages/indexes that work with uv).\n\n- The good thing is this *does* work to encapsulate binary builds and libraries, as long\n  as the binaries are included in the package.\n  It *doesn't* address the problem of external dependencies that traditionally need to\n  be installed outside the Python ecosystem (like ffmpeg).\n  (For this, [pixi](https://github.com/prefix-dev/pixi/) seems promising.)\n\n- This by default pre-compiles all files to create `__pycache__` .pyc files.\n  This means the build should start faster and could run on a read-only filesystem.\n  Use `--source-only` to have a source-only build.\n\n## FAQ\n\n- **Hasn't this been solved before?** Yes, by PyInstaller and other tools (see above).\n  But not as far as I know with the modern uv ecosystem, which has a lot of advantages\n  over legacy Python tooling.\n  The fact that this is so much simpler than PyInstaller arguably shows that a lot of\n  heavy lifting is being done by uv tooling.\n\n- **Why not just use Docker?** If you can, you probably should!\n  But there are lots of situations, such as in building apps for end-users on macOS and\n  Windows, where Docker is too heavyweight a solution.\n  And there are situations where you still want a single, portable package or\n  distribution that doesn't require runtime installs.\n\n* * *\n\n*This project was built from\n[simple-modern-uv](https://github.com/jlevy/simple-modern-uv).*\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjlevy%2Fpy-app-standalone","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjlevy%2Fpy-app-standalone","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjlevy%2Fpy-app-standalone/lists"}