Python
Starte im Assistenten mit einer Python-Vorlage oder wähl Python-Anwendung für ein leeres Projekt
oder ein Repository, oder nimm Python im Bereich Laufzeit zu einem
bestehenden Projekt dazu. Du bekommst einen Container namens envoryx-<projekt>-python aus dem Image
ghcr.io/envoryx/envoryx-python:<version>. Darin stecken pip, uv und git, dazu die
Build-Abhängigkeiten, die gängige Pakete brauchen, wenn es kein fertiges Wheel gibt. Er läuft als
PUID:PGID, mit deinem Projekt unter /var/www/html und einem Home-Verzeichnis unter /home/envoryx,
das Neustarts übersteht.
Deine Pakete wohnen in der virtuellen Umgebung des Projekts, /var/www/html/.venv. Der PATH des
Containers beginnt mit .venv/bin. Sobald die venv existiert, sind python, pip, gunicorn,
uvicorn und Co. also die aus deinem Projekt - im Terminal genauso wie per SSH. Ans Aktivieren musst du
nie denken.
Versionen
Abschnitt betitelt „Versionen“Standard ist 3.14. Außerdem gibt es 3.13, 3.12, 3.11 und 3.10, und 3.15 als Vorschau.
Die Version später zu wechseln ist ein größerer Schritt, als es aussieht, denn die .venv gehört zu
einer Python-Version: Ihre Pakete liegen in .venv/lib/python3.14/site-packages, und ein
3.13-Interpreter schaut dort nicht nach. Dein Server würde beim ersten Import sterben. Deshalb warten
der Anwendungsserver und die Python-Worker nach einem Wechsel mit einer Zeile im Log, bis die venv
wieder passt, und die Projektübersicht sagt dir, was los ist. Weil sie warten statt abzustürzen,
bleibt der Bereich Aktionen benutzbar, und dort liegt die Lösung: pip install -r requirements.txt
wirft die alte venv weg und baut eine neue aus der requirements.txt (in einem uv-Projekt erledigt
das uv sync). Vorher gibt die Aktion die Pakete aus, die in der alten venv lagen. Was du einmal mit
einem schlichten pip install installiert und nirgends festgehalten hast, steht in dieser Liste, du
kannst es also in die requirements.txt eintragen und neu installieren. Solange pip oder uv die neue
venv noch füllt, wartet der Server weiter, damit er nicht mittendrin startet und die Installation
mit sich reißt. Sobald die venv vollständig ist, startet er von selbst.
Den Anwendungsserver betreiben
Abschnitt betitelt „Den Anwendungsserver betreiben“Schalte Anwendungsserver ausführen ein, dann wird der Befehl der Vorlage zum Hauptprozess des Containers: Er startet automatisch neu, wenn er abstürzt, und bekommt einen eigenen Host-Port.


| Vorlage | Entwicklung | Produktion |
|---|---|---|
| Django | python manage.py runserver 0.0.0.0:<port> |
gunicorn <app> mit dem WSGI-Modul, z. B. config.wsgi:application |
| Flask | flask --app <app> run --debug |
gunicorn |
| FastAPI / ASGI | uvicorn <app> --reload |
uvicorn ohne Reload |
| WSGI | gunicorn <app> --reload |
gunicorn |
| Sonstige | python -m <modul>, liest HOST und PORT |
- |
Deine Anwendung gibst du als modul:attribut an, etwa main:app oder app:app. Für den
Produktionsmodus muss gunicorn oder uvicorn in der .venv installiert sein; darum kümmern sich die
Templates.
Der Container setzt FORWARDED_ALLOW_IPS=*. uvicorn und gunicorn glauben X-Forwarded-Proto und
X-Forwarded-For standardmäßig nur von 127.0.0.1, hier kommt aber jede Anfrage vom Proxy von
Envoryx im Projektnetz. Ohne die Variable baut FastAPI http://-URLs, und seine Weiterleitung für
den Schrägstrich am Ende wirft dich aus HTTPS raus.
Ohne PHP ist der Python-Server die Anwendung. https://<projekt>.<basis> und jede zusätzliche Domain
landen direkt bei ihm, der Port des Webserver-Containers bleibt zu, und Direktzugriff im Bereich Domains
ist der Python-Host-Port. Beliebt ist ein Django- oder FastAPI-Backend mit Vite-Frontend: Nimm einen
Node.js-Dev-Server dazu, und der behält <projekt>-dev.<basis> für sich. Neben PHP bekommt der
Python-Server nur seinen eigenen Host-Port. Der bleibt reserviert, während du den Server
ausschaltest, er kommt also mit derselben Nummer zurück, und deine Lesezeichen und
IDE-Run-Konfigurationen funktionieren weiter.
Ein ganz neues Projekt hat noch nichts zum Starten. Statt dauernd abzustürzen, wartet der Container, bis
die Einstiegsdatei auftaucht (manage.py bei Django, sonst <modul>.py oder ein Ordner <modul>/),
und danach, bis das Paket des Servers in der .venv liegt: django oder flask (im
Produktionsmodus für beide gunicorn), uvicorn bei ASGI, gunicorn bei WSGI. Im Log steht waiting for uvicorn in .venv, bis du
pip install -r requirements.txt oder uv sync im Bereich Aktionen ausführst, und genau das machst
du auch nach einem frischen Clone. Läuft gerade eine Installation mit pip oder uv, wartet der Server,
bis sie fertig ist.
Templates
Abschnitt betitelt „Templates“Django führt django-admin startproject config . aus und bereitet die Einstellungen für den Proxy
von Envoryx vor. Außerdem richtet es dj-database-url ein, sodass die eingespeiste DATABASE_URL
automatisch genutzt wird, und schickt Mails per SMTP an Mailpit, solange das Projekt es hat
(SMTP_HOST, SMTP_PORT). Danach führst du manage.py migrate im Bereich Aktionen aus.
Die Einstellungen bekommen außerdem WhiteNoise und STATIC_ROOT (staticfiles/), denn gunicorn
liefert im Produktionsmodus keine statischen Dateien aus: Ohne das antwortet /static/ mit 404, und
die Aktion manage.py collectstatic weiß nicht, wohin. Ein Django-Projekt, das vor Envoryx 0.18 aus
dem Template entstanden ist, braucht das von Hand: pip install whitenoise (und in die
requirements.txt damit), dann in config/settings.py ergänzen:
STATIC_ROOT = BASE_DIR / "staticfiles"MIDDLEWARE.insert(MIDDLEWARE.index("django.middleware.security.SecurityMiddleware") + 1, "whitenoise.middleware.WhiteNoiseMiddleware")WHITENOISE_USE_FINDERS = TrueWhiteNoise kümmert sich nur um statische Dateien, nicht um Uploads. Dateien unter MEDIA_URL liefern
weder gunicorn noch WhiteNoise aus, im Produktionsmodus antworten sie deshalb mit 404. Djangos Helfer
static() in der urls.py liefert sie nur im Entwicklungsmodus aus (ohne DEBUG tut er nichts, und
das Template koppelt DEBUG über DJANGO_DEBUG an den Laufzeitmodus). Und wenn du ein ImageField
einbaust: pip install pillow und ab damit in die requirements.txt, standardmäßig ist es nicht dabei.
Flask bringt eine app.py mit, die die App in ProxyFix von Werkzeug packt, damit
url_for(..., _external=True) und Weiterleitungen bei https://<projekt>.<basis> bleiben.
FastAPI bringt eine main.py mit der API-Doku unter /docs mit. Die Fehlerseite von Flask
zeigt im Debug-Modus den Traceback unter jeder URL, ihre interaktive Konsole nimmt aber nur Anfragen an
localhost oder 127.0.0.1 an. Auf dem Docker-Host selbst öffnest du dafür http://127.0.0.1:<port>
mit dem Python-Host-Port (Bereich Domains, Direktzugriff); die PIN, nach der sie fragt, steht im
Bereich Logs.
Alle drei legen die .venv an, installieren die Pakete und halten sie in requirements.txt fest. Nach
einem frischen Klon holt dir pip install im Bereich Aktionen so genau dieselbe Umgebung zurück.
Außerdem schreiben sie eine .gitignore für die .venv und die Bytecode-Caches, bei Django auch für
staticfiles/, media/ und die SQLite-Datei.
Debugging
Abschnitt betitelt „Debugging“debugpy-Port veröffentlichen im Bereich Laufzeit legt Port 5678 auf einen Host-Port - mit oder ohne Anwendungsserver. So kannst du auch ein Skript oder einen Testlauf debuggen, den du im Terminal startest. Der Bereich IDE zeigt Host, Port, Pfadzuordnung und fertige Schnipsel für die üblichen Server.
Envoryx veröffentlicht nur den Port; debugpy selbst ist deine Sache. Installier es in die .venv und
lass es von deiner App starten. Wo du das tust, ist wichtig, denn die Entwicklungsserver führen deinen
Code in einem Kindprozess aus, den ihr Reloader bei jeder Änderung neu startet. Ein debugpy vor dem
Server, wie bei python -m debugpy … manage.py runserver, säße im Elternprozess und würde dem Kind den
Port streitig machen. Starte es also nur im Kindprozess:
# Django: in manage.py, vor execute_from_command_line(...)if os.environ.get("RUN_MAIN") == "true": import debugpy; debugpy.listen(("0.0.0.0", 5678))
# Flask: ganz oben in app.pyimport osif os.environ.get("WERKZEUG_RUN_MAIN") == "true": import debugpy; debugpy.listen(("0.0.0.0", 5678))
# FastAPI / uvicorn --reload: ganz oben in main.py (nur der Worker importiert sie)import debugpy; debugpy.listen(("0.0.0.0", 5678))Dann verbindest du VS Code mit type: debugpy und request: attach. Jede Codeänderung startet den
Kindprozess neu und beendet die Sitzung, nach dem Speichern verbindest du dich also neu.
Der Python Debug Server von PyCharm funktioniert andersherum: Die IDE lauscht, und dein Code meldet
sich bei ihr. Im pydevd-pycharm-Snippet von PyCharm nimmst du host.docker.internal als Host, wenn
PyCharm auf dem Docker-Host läuft; jeder Runtime-Container löst diesen Namen zum Host auf. Läuft
PyCharm auf einem anderen Rechner, leitest du den Port mit dem SSH-Login des Projekts weiter
(ssh -R <port>:localhost:<port>, das auf dem localhost des Containers lauscht) und nimmst localhost.
Aktionen, Tests und Worker
Abschnitt betitelt „Aktionen, Tests und Worker“Im Bereich Aktionen gibt es python --version, python -m venv .venv, pip install -r requirements.txt
(legt die venv vorher an, falls sie fehlt), pip freeze, uv sync und uv lock bei vorhandener
pyproject.toml sowie Djangos migrate, makemigrations, collectstatic, check und flush.
pytest und der Test-Runner von Django werden im Bereich Tests erkannt - siehe
Aktionen und Tests. Djangos Runner und pytest-django
(pytest neben einer manage.py) legen ihre Testdatenbank als test_<datenbank> an. Der Projekt-Login von MySQL und MariaDB darf keine Datenbanken anlegen,
deshalb legt Envoryx diese vorher als Administrator an und gibt sie dem Login frei; deine
Entwicklungsdaten bleiben außen vor. Django liest DATABASE_URL über dj-database-url, und bei
PostgreSQL bekommt der Python-Container sie als postgresql://, die Schreibweise, die SQLAlchemy und
psycopg verstehen. Für Hintergrundjobs gibt es die Worker
Python-Script, Python-Modul, manage.py-Befehl (etwa rqworker oder qcluster), Celery-Worker
und Celery Beat, beschrieben unter Worker und Cron-Jobs.
Python später dazunehmen oder entfernen
Abschnitt betitelt „Python später dazunehmen oder entfernen“Die Python-Karte im Bereich Laufzeit hat einen Schalter Python aktivieren. Schaltest du ihn aus,
verschwinden der Python-Container und die Container der Python-Worker. Deine Dateien, die .venv und
die Worker-Definitionen bleiben, wo sie sind - schaltest du Python wieder ein, geht es genau dort
weiter.