Building a Python App
Expected structure
erebus detects a Python app by the presence of an entry point at the root:
app.py, main.py, server.py, __main__.py, manage.py, or an
extensionless script whose shebang is python / python3
(for example #!/usr/bin/env python3). Marker files pyproject.toml,
setup.py, and requirements.txt also select the Python runtime.
my_app/
app.py ← entry point, auto-detected
... ← other modules, templates, assets
Build
erebus build ./my_app -o my_app.erebus
The builder:
- detects the
pythonruntime and entrypoint (/app/app.py); - embeds the build machine’s
python3interpreter; - embeds the stdlib (
/usr/lib/pythonX.Y); - resolves
.sodependencies via the ELF analyzer (libc, etc.); - compresses and assembles the
.erebus.
Environment variables
The builder injects PYTHONUNBUFFERED=1 (real-time logs) and
PYTHONDONTWRITEBYTECODE=1 by default. Your app reads its own variables normally:
import os
PORT = int(os.environ.get("PORT", "8080"))
PORT=9000 ./my_app.erebus
Third-party dependencies (site-packages)
erebus embeds third-party dependencies from, in order of merge:
- a vendored
site-packages/directory at the app root (copied with the app); - a virtualenv
.venv/orvenv/at the app root → itslib/pythonX.Y/site-packages(orLib/site-packageson Windows layouts).
The full .venv / venv tree is not copied into the payload (it is on the
skip list with .git, __pycache__, …). Only the install tree is staged into
/app/site-packages.
my_app/
app.py
.venv/ ← skipped as a tree; site-packages extracted
lib/python3.12/site-packages/
bottle.py
The builder:
- merges those packages into
/app/site-packagesin the rootfs; - declares
PYTHONPATH=${ROOTFS}/app/site-packagesin metadata (the${ROOTFS}token is resolved by the launcher — see Launcher); - the stub also injects that
PYTHONPATHat runtime whenapp/site-packagesexists, so vendored-only apps work even without a prior metadata entry; - runs the ELF analyzer on
.sofiles from C extensions (numpy, pillow…) to embed their system dependencies.
If a .venv / venv is present without any Python entrypoint and without
requirements.txt, the build fails with a clear error (add app.py or
requirements.txt).
The example examples/site-packages-only has a vendored site-packages/ and
no requirements.txt. examples/bottle-web can use either a vendored tree
or pip at build time.
erebus build ./examples/site-packages-only -o /tmp/sp.erebus
./sp.erebus # → hello from vendored site-packages
Requirements.txt → pip install at build time
If your app has a requirements.txt with non-empty content, the builder
automatically pip-installs the dependencies into the rootfs site-packages
layout (and they remain importable via the embedded interpreter paths and/or
/app/site-packages):
erebus build ./my_app -o my_app.erebus
# [erebus] pip install: ./my_app/requirements.txt → …/site-packages
Supported entrypoints
The builder auto-detects the entrypoint in this priority order:
- Django —
manage.py→python3 /app/manage.py runserver 0.0.0.0:8000 - Streamlit —
streamlitin requirements →streamlit run /app/app.py --server.port 8501 --server.address 0.0.0.0 - FastAPI/ASGI —
uvicorn/gunicornin requirements orpyproject.toml→uvicorn module:app --host 0.0.0.0 --port 8000 - PEP 621
[project.scripts]—start = "..."inpyproject.toml - Python module —
pyproject.tomlwithtool.fastapi.entrypointor package with__main__.py→python3 -m module - Named script — first of
app.py,main.py,__main__.py,server.py→python3 /app/<script> - Shebang script — extensionless file with
#!/usr/bin/env python3(orpython) →python3 /app/<name>
Unsupported bundles
- PyInstaller onefile and
.pyzzipapps are detected and refused (fail closed). Package the Python sources (or a plain native binary) instead of re-wrapping a frozen bundle inside erebus.
Asyncio / multiprocessing under the sandbox
- asyncio (stdlib event loop,
asyncio.run, simplegather) works under the default sandbox. Seeexamples/asyncio-smoke. - multiprocessing / heavy
fork+ shared memory is a known limitation under Linux isolation ≥ 2 (pivot_root+ namespaces): some spawn methods fail or hang. Prefer threads or asyncio for packaged agents; do not claim full multiprocessing support.
Current limitations
- Cross-distro portability: see Isolation
— strongest when isolation uses user namespaces +
pivot_root.