- What is PyCrucible?
- Quickstart
- Installation
- Usage
- Runtime-Configuration
- Embedding-Options
- Github-Action
- Security
- How-it-works
- Features
- Community
- Changelog
- Thanks-to
PyCrucible packages any Python project into a single cross-platform executable with minimal overhead, powered by Rust and uv.
Existing tools like PyInstaller bundle the entire Python interpreter, dependencies, and project files. This typically results in:
- large binaries
- slow builds
- dependency complexity
- fragile runtime environments
- Fast and robust — written in Rust
- Multi-platform — Windows, Linux, macOS
- Tiny executables — ~2MB + your project files
- Hassle-free dependency resolution — delegated to uv
- Simple but configurable
- Supports auto-updates (GitHub public repos)
- Includes a GitHub Action for CI automation
Note
On the first run, PyCrucible downloads project dependencies through uv.
Subsequent runs start instantly.
If you dont want to read the specifics, jump right in. Its easy to use:
pip install pycrucible
pycrucible -e . -o ./myappThere are a couple of ways to install PyCrucible.
- using
PyPi - downloading from Github Releases
- compiling source code
PyCrucible is published to PyPi for every release. All you need to do is:
pip install pycrucibleTip
You can also use pipx, uv or uvx to download and install PyCrucible from PyPi.
Note
PyCrucible on PyPi is compiled with glibc==2.28 making sure its compatible with older operating systems.
You can download pre-made binaries for your system from
-
Ensure you have Rust installed.
-
Clone the repository
git clone https://github.com/razorblade23/PyCrucible
- Change directory to be inside of a project
cd PyCrucible
- Build Runner
cargo build -p pycrucible_runner --release
- Build Pycrucible
cargo build -p pycrucible --release
Note
The resulting binary will be in target/release/pycrucible.
All you need for starting is a single main.py file with some code.
Documentation can be found at PyCrucible docs.
This is our example project.
def main():
print("Hello from PyCrucible binary")
if __name__ == "__main__":
main()Important
PyCrucible defaults to src/main.py so that is how you should save your file, but you can override this with entrypoint configuration.
Tip
It would also be nice if you had pyproject.toml file, but this is not a requirement. If you do use configuration file, setting entrypoint directive is required.
Example pyproject.toml
[project]
name = "pycrucible-example"
version = "0.1.0"
description = "Simple example in using PyCrucible"
requires-python = ">=3.12"
dependencies = []
[tool.pycrucible]
entrypoint = "main.py"Now that we have our ingredients, lets make a binary using PyCrucible.
$ pycrucible -e .This will embed your project and produce a new binary which is by default called launcher (or launcher.exe on Windows).
Tip
To configure the output path and name of your binary, use -o or --output flag.
Example: pycrucible -e . -o ./myapp (or pycrucible -e . -o ./myapp.exe)
This is now all you need to distribute your python project to other people. No python required on their end. Just this single binary.
In web apps it is common to run some server that will handle your requests. In PyCrucible, entrypoint, pre-run or post-run do not need to be .py scripts.
You can use anything from your dependacies as a starting point. For example, you could run your flask application like this:
# pyproject.toml
...
[tool.pycrucible]
entrypoint = "gunicorn --port 9000 --host '0.0.0.0' app.app"Everything that you pass as entrypoint, pre-run or post-run will be used just like you would run those commands within your enviroment.
Configuration can be set in any of these files:
pycrucible.tomlpyproject.toml
Important
When using any configuration option, only entrypoint (or entry) is required. Other options are optional.
Supported configuration options are:
- entrypoint (entry) - The main file your application must run. Usually
main.pyorapp.py. - options
- debug - Enable debug output during runtime of binary. Used for debugging.
- extract_to_temp - Extract the project files to temporary directory instead of directory next to binary.
- delete_after_run - Delete source files after running.
- patterns
- include - What files to include into your final binary.
- exclude - What files to exclude from your final binary.
- env - key-value pairs of enviroment variables that will be set before running your binary.
- hooks
- pre-run - Run this script before running main application. Useful for pre-loading of data. Must be Python script.
- post-run - Run this script after running main application. Useful for unloading of data. Must be Python script.
Note
When both pycrucible.toml and pyproject.toml are discovered, configuration from pycrucible.toml will take effect.
Both of these files have exact same configuration options
You can find example configuration file for pycrucible.toml here
Only diffrence between these files is that pyproject.toml requires you to prefix pycrucible configuration with tool.pycrucible<.section>.
For example, if setting entrypoint
# pycrucible.toml
entrypoint = "src/main.py"
# or
entry = "src/main.py"
# pyproject.toml
[tool.pycrucible]
entrypoint = "src/main.py"
# or
entry = "src/main.py"If setting options
# pycrucible.toml
[options]
debug = false
extract_to_temp = false
delete_after_run = false
# pyproject.toml
[tool.pycrucible.options]
debug = false
extract_to_temp = false
delete_after_run = falseFull configuration options can be seen here:
pycrucible.toml
entrypoint = "src/main.py"
# or
entry = "src/main.py"
[options]
debug = false
extract_to_temp = false
delete_after_run = false
[patterns]
include = [
"**/*.py",
]
exclude = [
"**/__pycache__/**",
]
[env]
FOO = "foo"
BAR = "bar"
[hooks]
pre_run = "some_script.py"
post_run = "some_other_script.py"pyproject.toml
[tool.pycrucible]
entrypoint = "src/main.py"
# or
entry = "src/main.py"
[tool.pycrucible.options]
debug = false
extract_to_temp = false
delete_after_run = false
[tool.pycrucible.patterns]
include = [
"**/*.py",
]
exclude = [
"**/__pycache__/**",
]
[tool.pycrucible.env]
FOO = "foo"
BAR = "bar"
[tool.pycrucible.hooks]
pre_run = "some_script.py"
post_run = "some_other_script.py"Tip
You can use patterns to include or exclude any arbitrary files, like HTML templates, Kivy layout files or any other arbitrary files needed by your application.
For example your flask templates:
[tool.pycrucible]
entrypoint = "app.py"
[tool.pycrucible.patterns]
include = [
"**/*.py",
"src/templates/*.html",
"src/static/*.css",
"src/static/*.js",
]
exclude = [
"**/__pycache__/**",
]Warning
There is no need for setting PYTHONPATH env variable as uv will take care of this. If this is really needed, uv will complain and you should also also set UV_LINK_MODE="copy" as env variable to mitigate the warning.
Default configuration
This configuration takes place when no configuration is set by the user.entrypoint = "src/main.py"
# Options
debug = false
extract_to_temp = false
delete_after_run = false
# Patterns
patterns.include = [
"**/*.py",
]
patterns.exclude = [
".venv/**/*",
"**/__pycache__/**",
".git/**/*",
"**/*.pyc",
"**/*.pyo",
"**/*.pyd"
]
# Source repository (GitHub)
source = None
# Enviroment variables
env = None
# Pre and post run hooks
hooks = NoneIf any of these configuration options is not used, it will be replaced with default value.
Running pycrucible --help reveals more options:
$ pycrucible --help
Tool to generate python executable by melding UV and python source code in crucible of one binary
Usage: pycrucible [OPTIONS]
Options:
-e, --embed <EMBED>
Directory containing Python project to embed. When specified, creates a new binary with the embedded project
-o, --output <OUTPUT>
Output path for the new binary when using --embed [default: `./launcher`]
--uv-path <UV_PATH>
Path to `uv` executable. If not found, it will be downloaded automatically [default: `.`]
--debug
Enable debug output
-h, --help
Print help
-V, --version
Print versionPyCrucible has associated GitHub Action workflow which you can use to embed your python applications directly in CI.
Warning
This is an embedding tool. Like any other tool of this type, it may be used in distribution of un-trusted and/or malicius software.
The builder is the only distributed artifact; the Python projects themselves are provided by developers and has nothing to do with pycrucible or its authors or maintainers. Its is the sole responsibility of the developer and its end-users to confirm authenticity and trust in executing the binary.
Developers, when distributing binary to your end users, please:
- Verify your build environment is clean
- Sign binaries if distributing to external users (after embedding your project)
- Test your output on a clean VM for each platform
- Pin dependencies
- Set reproducible flags
Important
Make sure you run code signing after embedding your project. This makes sure that embedded project also be part of the signiture.
flowchart TD
%% Build Phase
A[PyCrucible CLI Builder] --> B[Is input a .whl file?]
B --> C[Embed wheel as-is]
B --> D[Archive project files]
C --> E[Payload Ready]
D --> E[Payload Ready]
E --> F[Embed uv runtime?]
F --> G[Embed uv]
F --> H[Do not embed uv]
G --> I[Build launcher binary]
H --> I[Build launcher binary]
%% Runtime Phase
I --> J[User runs launcher]
J --> K[Extract payload]
K --> L[Is uv embedded?]
L --> M[Use embedded uv]
L --> N[Locate or download uv]
M --> O[Run with uv]
N --> O[Run with uv]
O --> P[Run project entrypoint]
O --> Q[Run wheel file]
- Cross-Platform:
- Windows support
- macOS support (testing)
- Linux support
- Small overhead:
- Runner binary that embeds your project is just ~2 MB. This ofcourse grows with embedding
uvand your project.
- Runner binary that embeds your project is just ~2 MB. This ofcourse grows with embedding
- Configurable:
- Use
pycrucible.tomlorpyproject.tomlto customize embedding details- entrypoint
- include/exlude files
- arguments to
uv - env variables
- update source code from github
- pre and post run hooks (python scripts)
- extract to temporary directory (removes temporary directory after running automaticly)
- remove extracted files after running
- Support for multiple ways of defining requirements
-
uvinitializedpyproject.toml(This is preffered !) -
requirements.txt -
pylock.toml -
setup.py -
setup.cfg
-
- Load the project as a directory
- Runtime arguments are supported
- Use
- Tests:
- Unit tests covering as much as i can make it
We have an active community on Telegram, you are free to join us.
You can see latest changes at
The idea is inspired by Packaged.
Thanks to all the briliant developers at Astral.
They did awesome job with uv.
