Python-style code in a 1.47 MB executable
Kipferl — Bake Python CLI apps into fast, standalone binaries.
I want writing a little command-line tool to be easy. I also want giving it to somebody else to be easy. Annoyingly, these are two different wishes.
Python is lovely for the first one. For the second, I would like to hand over one small file and say “run this”. No Python installation, no virtual environment, no conversation about which pip belongs to which interpreter.
That is the idea behind Kipferl: Python-style code, packaged into one small executable.
How small? The status app below, with a box, a table and colored output, builds to 1,467,617 bytes on Apple Silicon. About 1.47 MB.
The services in the table are made up. The file size is measured.
What actually goes into the file
Putting a Python program into an executable is not a new idea. What I wanted was the combination: a pleasant high-level language, useful terminal UI pieces, and an output file small enough to feel right for a small tool. A five-line script should not need a removal company.
Kipferl uses PocketPy, an embeddable Python implementation, with a host written in Rust and a curated set of modules for command-line apps. It packages your program together with the runtime it needs. It does not compile arbitrary CPython code to machine code, and it does not promise that every package on PyPI will work.
That trade-off is where the size comes from. You get a focused way to build CLIs instead of a suitcase with an entire Python installation in it.
The whole program in the recording
This is the complete program:
import tui
tui.box("Three services. One small executable.", title="Release check")tui.table([ ["Service", "Status"], ["api", "ready"], ["worker", "ready"], ["coffee", "critical"],], headers=True)tui.success("Application ready. Coffee needs attention.")Build it, then run the result:
kipferl build status.py -o status./statusWhoever receives status needs neither Kipferl nor Python. The file still targets one operating system and architecture, so “standalone” does not mean the same file runs everywhere.
Two runtime profiles
Kipferl 0.6 looks at the imports and picks a prebuilt runtime profile, erring on the side of caution. The status app fits the smaller core profile. Building the exact same source with --full-runtime includes the complete runtime instead.
The table fits. Most of the runtime stays home.
These are measured outputs of the example above with the released 0.6.0 builder on macOS ARM64. MB means one million bytes. The source, the profile and the target platform all change the result, so 1.47 MB is a number you can reproduce, not a ceiling for every app.
The 0.6 release notes list a separate minimal-app baseline of 1,450,837 bytes. The Kipferl builder itself is a larger, separate artifact. It is not the file you give to your users.
This is profile selection. Unused functions do not disappear one by one. When the analyzer cannot safely pick the smaller profile, keeping everything working wins over saving a few bytes.
Why I wanted this
For me the appeal is practical. A repository helper, a status report, a small interactive utility: useful enough to share, not worth turning into an installation project of their own.
I want the easy part of writing Python and the easy part of handing over a small native tool to meet in the middle.
A Kipferl is a pastry. Handing someone the app should be about that much trouble.