Zum Inhalt springen

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.

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.

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.

Die Karte Python im Bereich Laufzeit mit dem laufenden Anwendungsserver, dem Entwicklungsmodus, dem Django-Preset, der WSGI-Anwendung und der Option für debugpyDie Karte Python im Bereich Laufzeit mit dem laufenden Anwendungsserver, dem Entwicklungsmodus, dem Django-Preset, der WSGI-Anwendung und der Option für debugpy
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.

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 = True

WhiteNoise 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.

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.py
import os
if 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.

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.

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.