Python Apps

Last updated

Native hosting for Python web apps. Each app is a per-user systemd unit running Gunicorn (WSGI) or Uvicorn (ASGI) on a loopback port, reverse-proxied by nginx at a mount point on the owner’s domain.

Framework catalog

Seven frameworks shipped, each with a scaffolded project template, pinned pip requirements, and the right server (gunicorn for WSGI, uvicorn for ASGI). Ordered by popularity.

Runtime

Interpreters supported when installed on the host: Python 3.8 – 3.14. The panel probes python<version> binaries via the agent (app.python.versions) and offers only versions that are actually installed. The probe is fail-open: if the agent is offline the version gate is skipped.

Runtime prerequisites installed once by install_python_apps_runtime (agent verb app.python.install_runtime):

python3 python3-venv python3-dev build-essential libffi-dev libssl-dev

server_settings.python_apps_enabled gates the entire feature. Users can’t create apps until an admin toggles it on.

App server types

TypeServerBind
wsgi (default)gunicorn --workers 3 --bind 127.0.0.1:<port>loopback
asgiuvicorn --host 127.0.0.1 --port <port> (--root-path <base_uri> if not /)loopback

The loopback port is auto-allocated from the [20000, 29999] pool. Uniqueness is enforced by uniq_python_apps_port. When the pool is exhausted the API returns 404.

Creating an app

POST /python-apps:

FieldRequiredNotes
domain_idyesDomain the app mounts under. Must be owned by the requesting user.
nameyesDisplay name.
python_versionyese.g. 3.11. Rejected if the interpreter isn’t on the host.
app_rootyesAbsolute path under the owning Linux user’s home; scope-validated by the agent.
app_typenowsgi (default) or asgi.
entrypointyesmodule:callable — e.g. myapp.wsgi:application.
base_urinoDefault /. Validated against /[A-Za-z0-9._/-]*.
envnoInitial environment map.
start_commandnoOverrides the derived Gunicorn/Uvicorn command.
cpu_limit / memory_limit / pids_limitnoPassed through to the systemd unit.

Venv & dependencies

  • Venv lives at <app_root>/venv/, created as the owning user with python<version> -m venv.
  • requirements.txt in app_root is installed automatically with pip install -r requirements.txt (8 min timeout per invocation, PIP_DISABLE_PIP_VERSION_CHECK=1).
  • Gunicorn or Uvicorn is always installed into the venv even if not in requirements.

Systemd unit

Written to /etc/systemd/system/jabali-app-<id>.service:

  • User — the owning tenant, not root.
  • Slice — nested under jabali-user-<username>.slice so package cgroup limits apply.
  • WorkingDirectoryapp_root.
  • Restarton-failure, 3 s delay.
  • SandboxNoNewPrivileges=true, PrivateTmp=true.
  • EnvironmentFile/etc/jabali/python-apps/<id>.env (mode 0640, owned by the app user). WSGI apps under a non-root base_uri get SCRIPT_NAME set here.

Nginx wiring

The API attaches a proxy_pass rule to the domain’s NginxRules array with:

  • Path — the app’s base_uri.
  • Targethttp://127.0.0.1:<loopback_port>.
  • Websocket — enabled.

The domain reconciler renders the vhost; the Python reconciler doesn’t touch nginx directly.

Package quota

hosting_packages.max_python_apps (unsigned int, default 0).

  • 0 — Python apps aren’t included in the package. Create returns python_apps_not_in_package.
  • >0 — the create endpoint counts existing apps and rejects with quota_exceeded when the cap is hit.
  • Admins bypass the check.

Lifecycle

ActionRESTAgent verb
ListGET /python-apps
CreatePOST /python-appsapp.python.apply (via reconciler)
GetGET /python-apps/:id
DeleteDELETE /python-apps/:idapp.python.remove
Start / stop / restart / statusPOST /python-apps/:id/controlapp.python.control
Tail logsGET /python-apps/:id/logs?lines=200 (max 1000)app.python.logs (journalctl -u jabali-app-<id>.service)
Set envPUT /python-apps/:id/envapp.python.apply (rerun)
List installed interpretersGET /python-apps/versionsapp.python.versions

Delete stops and removes the unit and env file. The venv is intentionally preserved.

Reconciler

reconcile_python_apps calls app.python.apply for every eligible row on each cycle. It converges venv, dependencies, unit file, and unit state, then writes status and last_error back to the row. Status values: pendingrunning | stopped | failed.

CLI

jabali python-app create
jabali python-app list
jabali python-app control <id> start|stop|restart|status
jabali python-app logs <id>
jabali python-app env get|set <id>
jabali python-app delete <id>

Caveats

  • No health checks beyond systemd Restart=on-failure.
  • No backup of the app tree by this feature — snapshot via Backups.
  • Loopback bind only; no direct port publishing.

See also: Applications (PHP apps), Docker Apps, Hosting Packages.