Das Deployen eines Flask PostgreSQL Nginx-Stacks ist der Standardweg, um eine Python-Webanwendung vom Entwicklungsrechner auf einen produktiven Server zu bringen. Diese Anleitung durchläuft jede Schicht: eine PostgreSQL-Datenbank, Gunicorn als WSGI-Applikationsserver, systemd für den dauerhaften Betrieb und Nginx als Reverse Proxy, der dem Internet zugewandt ist. Am Ende hast du ein gehärtetes, produktionsreifes Setup, auf dem du aufbauen kannst.
Voraussetzungen
- Ubuntu 22.04 oder 24.04 Server (root- oder sudo-Zugang)
- Ein Domain-Name, der auf die Server-IP zeigt (optional, für SSL später)
- Grundlegende Kenntnisse der Linux-Kommandozeile und Python
- Eine Flask-Anwendung, die deployt werden soll (oder das minimale Beispiel aus dieser Anleitung verwenden)
Systemabhängigkeiten installieren
Beginne damit, den Paketindex zu aktualisieren und alles in einem Durchgang zu installieren:
sudo apt update && sudo apt upgrade -y
sudo apt install -y python3 python3-pip python3-venv \
postgresql postgresql-contrib \
nginx curl
Installationen überprüfen:
python3 --version # Python 3.10+
psql --version # psql 14+
nginx -v # nginx/1.22+
PostgreSQL einrichten
Datenbank und Benutzer erstellen
Wechsle zum postgres-Systemkonto und öffne die interaktive Shell:
sudo -u postgres psql
Erstelle innerhalb von psql einen dedizierten Benutzer und eine Datenbank. Verwende niemals den Standard-postgres-Superuser für Anwendungsverbindungen:
CREATE USER flaskapp WITH PASSWORD 'StrongPassw0rd!';
CREATE DATABASE flaskdb OWNER flaskapp;
GRANT ALL PRIVILEGES ON DATABASE flaskdb TO flaskapp;
\q
Verbindung testen
Prüfe die Verbindung, bevor du sie in Flask einbindest:
psql -U flaskapp -h 127.0.0.1 -d flaskdb -c "\conninfo"
Du solltest eine Bestätigung sehen, dass die Verbindung erfolgreich war. Falls nicht, prüfe, ob pg_hba.conf md5- oder scram-sha-256-Authentifizierung für lokale Verbindungen erlaubt:
sudo nano /etc/postgresql/14/main/pg_hba.conf
# Stelle sicher, dass diese Zeile vorhanden ist:
# host all all 127.0.0.1/32 scram-sha-256
sudo systemctl restart postgresql
Die Flask-Anwendung deployen
Projektstruktur
Platziere die Anwendung in /srv/flaskapp. Die Verwendung von /srv hält Web-Apps von Systemdateien getrennt:
/srv/flaskapp/
├── venv/
├── app/
│ ├── __init__.py
│ └── models.py
├── wsgi.py
├── .env
└── requirements.txt
Virtualumgebung erstellen
sudo mkdir -p /srv/flaskapp
sudo chown $USER:$USER /srv/flaskapp
cd /srv/flaskapp
python3 -m venv venv
source venv/bin/activate
Python-Pakete installieren
pip install flask flask-sqlalchemy psycopg2-binary gunicorn python-dotenv
pip freeze > requirements.txt
Minimale Flask-Anwendung
Erstelle app/__init__.py:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import os
db = SQLAlchemy()
def create_app():
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = os.environ["DATABASE_URL"]
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.config["SECRET_KEY"] = os.environ["SECRET_KEY"]
db.init_app(app)
@app.route("/")
def index():
return "Flask + PostgreSQL + Nginx is running."
return app
Erstelle wsgi.py (den Einstiegspunkt, den Gunicorn aufruft):
from app import create_app
app = create_app()
if __name__ == "__main__":
app.run()
Umgebungsvariablen
Speichere Secrets in /srv/flaskapp/.env. Committe diese Datei niemals in die Versionskontrolle:
DATABASE_URL=postgresql://flaskapp:StrongPassw0rd!@127.0.0.1:5432/flaskdb
SECRET_KEY=replace-with-a-long-random-string
FLASK_ENV=production
Berechtigungen sofort einschränken:
chmod 600 /srv/flaskapp/.env
Gunicorn manuell testen
Bevor systemd eingerichtet wird, prüfe, ob Gunicorn die App starten kann:
source /srv/flaskapp/venv/bin/activate
cd /srv/flaskapp
gunicorn --workers 3 --bind 0.0.0.0:8000 wsgi:app
Öffne http://deine-server-ip:8000 — du solltest die Flask-Antwort sehen. Wenn ja, stoppe Gunicorn mit Ctrl+C und fahre mit systemd fort.
Gunicorn als systemd-Dienst
Gunicorn unter systemd zu betreiben bietet automatische Neustarts bei Absturz, Start beim Booten und zentrale Protokollierung über journalctl.
Worker-Anzahl wählen
| Server-RAM | Empfohlene Worker |
|---|---|
| 512 MB | 2 |
| 1 GB | 3 |
| 2 GB | 5 |
| 4 GB+ | 9 |
Eine gängige Formel: (2 × CPU-Kerne) + 1. Drei Worker sind ein sicherer Standardwert für die meisten kleinen VPS-Instanzen.
Service-Unit-Datei erstellen
sudo nano /etc/systemd/system/flaskapp.service
Folgendes einfügen — Pfade und Benutzer nach Bedarf anpassen:
[Unit]
Description=Gunicorn instance for Flask app
After=network.target postgresql.service
[Service]
User=www-data
Group=www-data
WorkingDirectory=/srv/flaskapp
EnvironmentFile=/srv/flaskapp/.env
ExecStart=/srv/flaskapp/venv/bin/gunicorn \
--workers 3 \
--bind unix:/run/flaskapp/gunicorn.sock \
--access-logfile /var/log/flaskapp/access.log \
--error-logfile /var/log/flaskapp/error.log \
wsgi:app
RuntimeDirectory=flaskapp
RuntimeDirectoryMode=0755
[Install]
WantedBy=multi-user.target
Wichtige Felder erklärt:
EnvironmentFile— lädt deine.env-Secrets in die ProzessumgebungRuntimeDirectory— systemd erstellt/run/flaskapp/automatisch und bereinigt es beim StoppenAfter=postgresql.service— stellt sicher, dass die Datenbank läuft, bevor die App startetunix:-Socket — schneller als TCP für die lokale Nginx-zu-Gunicorn-Kommunikation
Log-Verzeichnis erstellen und Besitzer anpassen:
sudo mkdir -p /var/log/flaskapp
sudo chown www-data:www-data /var/log/flaskapp
sudo chown -R www-data:www-data /srv/flaskapp
Dienst aktivieren und starten:
sudo systemctl daemon-reload
sudo systemctl enable flaskapp
sudo systemctl start flaskapp
sudo systemctl status flaskapp
Es sollte Active: active (running) erscheinen. Prüfen, ob der Socket erstellt wurde:
ls -la /run/flaskapp/gunicorn.sock
Nginx als Reverse Proxy konfigurieren
Nginx sitzt vor Gunicorn, verarbeitet eingehenden HTTP/HTTPS-Traffic und leitet Anwendungsanfragen an den Unix-Socket weiter.
Site-Konfiguration erstellen
sudo nano /etc/nginx/sites-available/flaskapp
server {
listen 80;
server_name example.com www.example.com;
# Statische Dateien werden direkt von Nginx ausgeliefert — kein Gunicorn-Overhead
location /static/ {
alias /srv/flaskapp/app/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
location / {
proxy_pass http://unix:/run/flaskapp/gunicorn.sock;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Verhindert, dass langsame Clients Gunicorn-Worker blockieren
proxy_read_timeout 90;
proxy_connect_timeout 90;
}
}
Site aktivieren und Konfiguration testen:
sudo ln -s /etc/nginx/sites-available/flaskapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
SSL mit Certbot hinzufügen
Für den Produktionsbetrieb TLS immer bei Nginx terminieren. Certbot automatisiert die Zertifikatsausstellung und -erneuerung:
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
Certbot schreibt die Nginx-Konfiguration mit SSL-Direktiven um und richtet die automatische Erneuerung über einen systemd-Timer ein.
Praxisbeispiel
Du betreibst eine SaaS-Anwendung auf Basis von Flask, die Kundenbestellungen in PostgreSQL speichert. Der Traffic erreicht mittags mit ca. 200 gleichzeitigen Nutzern seinen Höhepunkt. Ohne Nginx-Pufferung würden langsame mobile Clients Gunicorn-Worker über 30 Sekunden lang belegen und Request-Timeouts verursachen. Mit dem Setup aus dieser Anleitung:
- Nginx akzeptiert die Verbindung sofort und puffert den langsamen Client
- Es leitet den vollständigen Request-Body in Millisekunden an Gunicorn weiter
- Gunicorn verarbeitet die Anfrage und schreibt die Antwort zurück an Nginx
- Nginx streamt die Antwort an den langsamen Client, während Gunicorn bereits für die nächste Anfrage frei ist
Drei Gunicorn-Worker können nun weit mehr als drei gleichzeitige Benutzer bedienen. Füge weitere Worker hinzu oder wechsle zu gevent-Async-Workern, wenn du mehr Skalierung benötigst.
Fallstricke und Sonderfälle
Unix-Socket-Berechtigungen: Der Nginx-www-data-Benutzer muss den Gunicorn-Socket lesen können. Beide unter www-data auszuführen (wie oben gezeigt) ist die einfachste Lösung. Falls sie als unterschiedliche Benutzer laufen, www-data zur App-Gruppe hinzufügen und den Socket auf 660 setzen.
DATABASE_URL mit Sonderzeichen: Passwörter mit @, / oder % müssen in der URL prozent-kodiert werden. Alternativ individuelle Umgebungsvariablen (PGUSER, PGPASSWORD usw.) verwenden und die URL in Python zusammenbauen.
Fehlendes python-dotenv in der Produktion: EnvironmentFile in systemd liest die .env direkt — für den systemd-Pfad wird python-dotenv nicht benötigt. Es wird gebraucht, wenn flask run lokal ausgeführt wird.
App-Factory-Muster vs. Modul-Level-App: Gunicorns wsgi:app erwartet ein Callable. Mit dem Factory-Muster die erstellte App auf Modulebene in wsgi.py wie oben gezeigt bereitstellen. create_app() nicht innerhalb einer Funktion aufrufen, ohne das Ergebnis zu exponieren.
PostgreSQL max_connections: Jeder Gunicorn-Worker öffnet seine eigene Datenbankverbindung. Mit 9 Workern auf 4 Servern könnte das Standard-100-Verbindungslimit erreicht werden. PgBouncer als Connection-Pooler vor PostgreSQL einsetzen bei Deployments mit vielen Workern.
Fehlerbehebung
502 Bad Gateway von Nginx:
Prüfen, ob der Gunicorn-Dienst läuft und der Socket vorhanden ist:
sudo systemctl status flaskapp
ls /run/flaskapp/gunicorn.sock
Auch Nginx-Fehlerprotokolle prüfen: sudo tail -50 /var/log/nginx/error.log
[CRITICAL] WORKER TIMEOUT in Gunicorn-Logs:
Eine Anfrage dauert länger als das Standard-30-Sekunden-Timeout. Mit --timeout 120 erhöhen oder die auslösende langsame Abfrage optimieren.
OperationalError: could not connect to server:
PostgreSQL läuft nicht oder die DATABASE_URL ist falsch. Den Verbindungsstring direkt mit psql $DATABASE_URL testen.
Permission denied am Socket:
Benutzer-Mismatch zwischen Nginx und Gunicorn. Prüfen, ob beide als www-data laufen:
ps aux | grep -E "gunicorn|nginx" | awk '{print $1, $11}'
Statische Dateien liefern 404:
Der alias-Pfad in Nginx muss dem tatsächlichen statischen Verzeichnis entsprechen. Hinweis: alias erfordert einen abschließenden Schrägstrich; root nicht.
Zusammenfassung
- PostgreSQL installieren, einen dedizierten Benutzer und eine Datenbank erstellen und
.env-Dateiberechtigungen einschränken, um Zugangsdaten zu schützen - Flask unter Gunicorn mit einem Unix-Socket betreiben — Gunicorn niemals direkt dem Internet aussetzen
- Eine systemd-
[Service]-Unit mitEnvironmentFileundRuntimeDirectoryfür sauberes Prozessmanagement und automatischen Neustart verwenden - Nginx so konfigurieren, dass
/an den Gunicorn-Socket weitergeleitet und/static/direkt ausgeliefert wird, um unnötige Applikationsserver-Last zu eliminieren - SSL über Certbot sofort hinzufügen — reines HTTP ist im Produktionsbetrieb nie akzeptabel
- Auf Verbindungspool-Erschöpfung bei PostgreSQL beim Skalieren von Gunicorn-Workern achten; bei Bedarf PgBouncer einsetzen