9.9 KiB
DEPLOYMENT.md — Projekt-Hub auf dem VPS installieren
Diese Anleitung beschreibt, wie die fertige Flask-App (siehe CLAUDE.md und
TASKLISTE.md) auf einem Ubuntu-VPS produktiv läuft. Zielarchitektur:
Internet → nginx (Port 80/443, TLS) → gunicorn (Unix-Socket) → Flask-App → SQLite
systemd sorgt dafür, dass die App automatisch beim Booten startet und bei einem Absturz neu startet.
Vorausgesetzt: Ubuntu-VPS mit SSH-Zugang und sudo-Rechten. Domain ist optional — diese Anleitung funktioniert zunächst auch nur mit der Server-IP (HTTPS dann erst, sobald eine Domain feststeht).
1. System vorbereiten
sudo apt update && sudo apt upgrade -y
sudo apt install -y python3 python3-venv python3-pip nginx git
Eigenen, nicht-root Nutzer für die App anlegen (falls noch nicht vorhanden):
sudo adduser projekthub
sudo usermod -aG sudo projekthub # optional, falls dieser Nutzer auch administrieren soll
Ab hier alles als projekthub-Nutzer ausführen (su - projekthub), außer wo
explizit sudo für Systemdienste nötig ist.
2. Code auf den Server bringen
Variante A — über Git (empfohlen, falls Repo z.B. auf einem privaten Git-Server oder GitHub liegt):
cd ~
git clone <DEIN_REPO_URL> projekt-hub
cd projekt-hub
Variante B — Dateien direkt hochladen (z.B. von deinem Rechner aus):
scp -r projekt-hub/ projekthub@DEINE_SERVER_IP:~/projekt-hub
3. Python-Umgebung einrichten
cd ~/projekt-hub
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
gunicorn und der Redis-Client sind bereits in requirements.txt enthalten —
kein separates Installieren nötig. Der Redis-Server (für Rate-Limiting bei
mehreren Workern, siehe Abschnitt 8a) ist ein Systemdienst und wird separat
installiert:
sudo apt install -y redis-server
sudo systemctl enable --now redis-server
4. Konfigurationsdatei .env anlegen
cp .env.example .env
nano .env
Wichtige Werte für die Produktion:
FLASK_ENV=production
SECRET_KEY=<langer-zufaelliger-string>
# Relativer sqlite-Name wird automatisch in den instance/-Ordner aufgelöst
# (-> instance/hub.sqlite). Keinen instance/-Pfad voranstellen.
DATABASE_URL=sqlite:///hub.sqlite
# Login
ALLOWED_EMAILS=deine@adresse.com
# Rate-Limiting über gemeinsames Backend (mehrere Worker), siehe Abschnitt 8a
RATELIMIT_STORAGE_URI=redis://localhost:6379
# Mail (siehe Abschnitt 7)
MAIL_SERVER=127.0.0.1
MAIL_PORT=1025
MAIL_USERNAME=deine@adresse.com
MAIL_PASSWORD=<bridge-oder-relay-passwort>
MAIL_USE_TLS=true
# Domain (sobald bekannt, siehe Abschnitt 9)
SERVER_NAME=
SESSION_COOKIE_SECURE=true
Einen zufälligen SECRET_KEY generieren:
python3 -c "import secrets; print(secrets.token_hex(32))"
.env darf niemals ins Git-Repo committen — sicherstellen, dass .gitignore
.env enthält.
5. Datenbank initialisieren
mkdir -p instance
flask db upgrade
Prüfen, dass instance/hub.sqlite existiert:
ls -la instance/
6. Testlauf mit dem Flask-Dev-Server (nur zur Kontrolle)
flask run --host=0.0.0.0 --port=5000
Im Browser http://DEINE_SERVER_IP:5000 aufrufen — die Login-Seite sollte
erscheinen. Danach mit Strg+C wieder stoppen; der Dev-Server ist nicht für
Dauerbetrieb gedacht.
7. E-Mail-Versand über Proton einrichten
Option A: Proton Mail Bridge (Docker)
Die Bridge initialisiert beim Start lokale IMAP/SMTP-Server, über die die App Mails verschicken kann. Auf dem VPS läuft sie containerisiert:
sudo apt install -y docker.io
sudo systemctl enable --now docker
docker volume create proton-bridge
docker run -d --name proton-bridge \
-v proton-bridge:/root \
-p 127.0.0.1:1025:25 \
-p 127.0.0.1:1143:143 \
spameier/proton-bridge
Danach einmalig in den Container einloggen und die Bridge mit den Proton-Zugangsdaten verbinden:
docker exec -it proton-bridge bridge --cli
# im Bridge-CLI: "login" eingeben und den Anweisungen folgen
Die Bridge zeigt danach lokale SMTP-Zugangsdaten (Benutzername + generiertes
Passwort) an — diese in die .env unter MAIL_USERNAME / MAIL_PASSWORD
eintragen, MAIL_SERVER=127.0.0.1, MAIL_PORT=1025.
Bekannte Stolpersteine: Proton Bridge nutzt ein selbstsigniertes Zertifikat und initiiert den TLS-Handshake selbst, was bei direkter Relay-Einbindung zu Problemen führen kann. Falls der Mailversand aus Flask nicht klappt, hilft es, Postfix als lokalen Zwischen-Relay zu betreiben, der an die Bridge weiterleitet — oder direkt auf Option B auszuweichen.
Option B: EU-SMTP-Relay als Fallback
Falls Option A zu instabil ist: bei einem europäischen Transactional-Mail-Anbieter
(z.B. Infomaniak, Mailjet EU) ein kostenloses/günstiges Konto anlegen, SMTP-Zugangsdaten
erhalten und in .env eintragen — MAIL_SERVER, MAIL_PORT, MAIL_USERNAME,
MAIL_PASSWORD, MAIL_USE_TLS entsprechend anpassen. Am Flask-Code ändert sich
nichts, da die App generisch über Flask-Mail/SMTP konfiguriert ist.
Testmail verschicken
flask shell
from app.extensions import mail
from flask_mail import Message
msg = Message("Testmail Projekt-Hub", recipients=["deine@adresse.com"])
msg.body = "Funktioniert!"
mail.send(msg)
8. gunicorn + systemd einrichten
Die Gunicorn-Konfiguration liegt versioniert im Repo: gunicorn.conf.py
(Worker-Anzahl, Socket, Logging). Startbefehl testen (im .venv):
gunicorn -c gunicorn.conf.py wsgi:app
Nginx an Gunicorn über einen Unix-Socket statt TCP-Port anzubinden reduziert den TCP-Overhead und ist die bevorzugte Variante, wenn beide auf demselben Host laufen. Mit Strg+C stoppen, danach den systemd-Service einrichten.
Die systemd-Unit liegt ebenfalls im Repo (deploy/projekt-hub.service) und
lädt die Gunicorn-Config per -c. Kopieren und ggf. Pfade/Nutzer anpassen:
sudo cp deploy/projekt-hub.service /etc/systemd/system/projekt-hub.service
sudo nano /etc/systemd/system/projekt-hub.service # Pfade/Nutzer prüfen
Die Worker-Anzahl steuert gunicorn.conf.py (Default: (2 × CPU-Kerne) + 1,
per env GUNICORN_WORKERS überschreibbar).
Service aktivieren und starten:
sudo systemctl daemon-reload
sudo systemctl enable projekt-hub
sudo systemctl start projekt-hub
sudo systemctl status projekt-hub
8a. Redis für Rate-Limiting
Flask-Limiter (Login-Code-Anfragen) braucht bei mehreren gunicorn-Workern ein
gemeinsames Speicher-Backend — sonst zählt jeder Worker eigene Limits und das
Limit greift faktisch nicht. Redis wurde in Abschnitt 3 installiert; in der .env
verweist RATELIMIT_STORAGE_URI darauf:
RATELIMIT_STORAGE_URI=redis://localhost:6379
Damit Redis vor der App startet, in der systemd-Unit (auskommentierte Zeilen aktivieren):
After=network.target redis-server.service
Wants=redis-server.service
Schnelltest, dass Redis läuft: redis-cli ping → PONG. Wer bewusst nur einen
Worker fährt, kann auch bei memory:// bleiben.
Logs ansehen:
sudo journalctl -u projekt-hub -f
9. nginx als Reverse Proxy
Die nginx-Vorlage liegt im Repo: deploy/nginx/projekt-hub.conf (Socket-Proxy
- Static-Auslieferung). Kopieren und
server_name(zunächst Server-IP) anpassen:
sudo cp deploy/nginx/projekt-hub.conf /etc/nginx/sites-available/projekt-hub
sudo nano /etc/nginx/sites-available/projekt-hub # server_name / Pfade prüfen
Aktivieren:
sudo ln -s /etc/nginx/sites-available/projekt-hub /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Im Browser http://DEINE_SERVER_IP aufrufen — die Login-Seite sollte jetzt über
nginx ausgeliefert werden.
10. Domain & HTTPS (sobald die Domain feststeht)
- DNS A-Record der gewählten Domain/Subdomain auf die Server-IP zeigen lassen
- In der nginx-Config
server_nameauf die Domain ändern,nginx -t+systemctl reload nginx - Certbot installieren und Zertifikat holen:
Certbot passt die nginx-Config automatisch für HTTPS + Redirect von HTTP an.sudo apt install -y certbot python3-certbot-nginx sudo certbot --nginx -d deine-domain.de - In
.env:SERVER_NAME=deine-domain.de,SESSION_COOKIE_SECURE=true,PREFERRED_URL_SCHEME=httpssetzen, danach App neu starten:sudo systemctl restart projekt-hub
11. Updates einspielen
Wenn neue Features (z.B. weitere Module) hinzukommen:
cd ~/projekt-hub
git pull # oder neue Dateien per scp hochladen
source .venv/bin/activate
pip install -r requirements.txt # falls neue Abhängigkeiten
flask db upgrade # falls neue Migrationen
sudo systemctl restart projekt-hub
12. Backup
Die gesamte App-Datenbank ist eine einzelne SQLite-Datei. Einfaches Backup per Cronjob (täglich, Beispiel):
crontab -e
Zeile hinzufügen:
0 3 * * * cp /home/projekthub/projekt-hub/instance/hub.sqlite /home/projekthub/backups/hub-$(date +\%F).sqlite
Vorher den Ordner anlegen: mkdir -p ~/backups. Da SQLite auch während Schreib-
zugriffen kopiert werden kann, ist für ein konsistentes Backup sqlite3 hub.sqlite ".backup '/pfad/hub-$(date +%F).sqlite'" die sauberere Variante.
Hinweis: Der frühere JSON-Export wurde durch den PDF-Export ersetzt (eine druckbare, projektbasierte To-Do-Liste, kein Datensicherungs-Format). Die Datensicherung erfolgt also ausschließlich über die SQLite-Datei wie oben.
13. Sicherheits-Checkliste (kurz)
.envhat restriktive Dateirechte:chmod 600 .envFLASK_ENV=productiongesetzt (kein Debug-Modus)- Firewall (
ufw) aktiv, nur Ports 22, 80, 443 offen:sudo ufw allow OpenSSH sudo ufw allow 'Nginx Full' sudo ufw enable - SSH-Login per Passwort deaktiviert, nur Key-Auth (falls noch nicht der Fall)
- Regelmäßige
apt upgradeeinplanen (z.B.unattended-upgrades)