Der Admin-Benutzer wird immer in der Datenbank angelegt, bevor versucht wird, die generierten Zugangsdaten in ZUGANGSDATEN.md zu schreiben. Schlug dieser Schreibvorgang fehl (z. B. EACCES, weil die per Bind-Mount eingebundene Host-Datei root gehört und der App-Container als nicht-root läuft), flog bisher eine ungefangene Exception – das Skript brach ab, BEVOR das Passwort irgendwo protokolliert wurde. Der Admin existierte dann in der DB, aber niemand kannte sein Passwort mehr. appendFileSync() läuft jetzt in einem try/catch; schlägt der Dateizugriff fehl, werden E-Mail und Passwort stattdessen direkt ins Log geschrieben, und das Skript beendet sich weiterhin mit Exitcode 0 (die eigentlich kritische Operation – der DB-Insert – ist ja bereits erfolgreich). Lokal verifiziert: Skript bricht bei einer nicht beschreibbaren ZUGANGSDATEN.md nicht mehr ab, Zugangsdaten landen im Log, zweiter Lauf bleibt idempotent. DEPLOYMENT.md: Schritt 2 ergänzt um "chmod 666 ZUGANGSDATEN.md" (der eigentliche Auslöser), neue Fehlerbehebung-Einträge für ECONNREFUSED-Startreihenfolge und diesen EACCES-Fall inkl. Recovery-Anleitung für einen bereits verwaisten Admin-Account.
8.7 KiB
Deployment auf einem neuen Server
Schritt-für-Schritt-Anleitung, um Canteen Menu auf einem frischen (Linux-)Server produktiv zu betreiben. Die App läuft vollständig selbst gehostet (PostgreSQL + eigene Auth), einzige optionale externe Abhängigkeit ist DeepL für automatische Übersetzungen.
Voraussetzungen
- Ein Server (VM oder Root-Server) mit Docker und Docker Compose v2
(
docker compose versionsollte funktionieren; auf den meisten aktuellen Distributionen perapt install docker.io docker-compose-v2bzw. dem offiziellen Docker-Install-Skript installierbar). - Eine Domain, deren DNS-A/AAAA-Eintrag auf die Server-IP zeigt (z. B.
menu.example.de). Ohne eigene Domain funktioniert die App auch unter der reinen Server-IP, dann aber ohne gültiges HTTPS-Zertifikat und ohne funktionierende Passkeys (siehe unten). - Mindestens 1 GB RAM frei (App + PostgreSQL sind genügsam, für ~250 Mitarbeiter reicht die kleinste übliche VM-Größe).
1. Repository auf den Server holen
git clone https://github.com/<dein-fork-oder-repo>/canteen-menu.git
cd canteen-menu
(Oder per git pull/scp, falls kein direkter Git-Zugriff vom Server
aus gewünscht ist.)
2. Konfiguration anlegen
cp .env.example .env.local
.env.local mit einem Editor öffnen und ausfüllen:
| Variable | Was rein muss |
|---|---|
LOCAL_PG_PASSWORD |
Ein neues, zufälliges Passwort nur aus Buchstaben/Zahlen (z. B. openssl rand -hex 24) – wird beim ersten Start zum Datenbank-Passwort des mitgelieferten PostgreSQL-Containers. Zeichen wie #, $, Leerzeichen oder Anführungszeichen können beim Einlesen der .env-Datei zu Problemen führen (# beginnt z. B. einen Kommentar) |
APP_URL |
Die öffentliche URL mit Schema, z. B. https://menu.example.de (auch wenn HTTPS erst in Schritt 5 eingerichtet wird – schon jetzt korrekt eintragen, da daraus u. a. die Passkey-Domain abgeleitet wird) |
SITE_TITLE |
Optional, Titel im Browser-Tab/Überschrift ohne Logo |
ADMIN_EMAIL |
Optional, E-Mail des automatisch angelegten Admin-Erstnutzers. Ohne Angabe: admin@<Hostname aus APP_URL> |
DEEPL_API_KEY |
Optional, nur für automatische Übersetzung |
DATABASE_URL in .env.local nicht anpassen – die wird nur für
den Betrieb ohne Docker gebraucht (siehe README) und beim
Docker-Compose-Start ignoriert.
Zusätzlich eine leere Datei für die automatisch generierten Admin-Zugangsdaten anlegen, da sie per Bind-Mount in den Container eingebunden wird. Der App-Container läuft als nicht-root-Nutzer – damit er die Datei beschreiben darf, muss sie für alle schreibbar sein:
touch ZUGANGSDATEN.md
chmod 666 ZUGANGSDATEN.md
3. App starten
Wichtig: .env.local muss vollständig ausgefüllt sein, bevor
dieser Befehl zum ersten Mal läuft – und danach immer mit
--env-file .env.local aufgerufen werden. PostgreSQL setzt sein
Passwort nur beim allerersten Start auf einem leeren Datenverzeichnis;
läuft der Befehl vorher schon einmal ohne (oder mit einem anderen)
--env-file, landet ein falsches Passwort dauerhaft im Datenbank-Volume
(Symptom: password authentication failed for user "postgres" in den
App-Logs, siehe Fehlerbehebung).
docker compose --env-file .env.local up -d --build
Das baut das Docker-Image, startet PostgreSQL und die App, wendet beim ersten Start automatisch das Datenbankschema an und legt – da die Datenbank leer ist – einen einzigen Admin-Benutzer mit zufällig generiertem Passwort an.
Kurz prüfen, ob beides läuft:
docker compose ps
docker compose logs app --tail 30
In den Logs sollte u. a. bootstrap-admin – Admin angelegt: ...
stehen.
4. Admin-Zugangsdaten abholen
cat ZUGANGSDATEN.md
Enthält E-Mail-Adresse und Passwort des automatisch angelegten Admin-Kontos. Direkt nach dem ersten Login unter „Mein Konto“ das Passwort ändern und optional einen Passkey registrieren.
Die App ist jetzt unter http://<server-ip>:3000 erreichbar – für den
produktiven Betrieb fehlt noch HTTPS/die Domain (nächster Schritt).
5. Reverse-Proxy + HTTPS einrichten
Die App selbst spricht nur HTTP auf Port 3000; TLS/HTTPS übernimmt ein Reverse-Proxy davor. Am einfachsten mit Caddy (automatisches Let’s-Encrypt-Zertifikat, keine manuelle Zertifikats- verwaltung nötig):
sudo apt install caddy # oder: siehe caddyserver.com/docs/install
/etc/caddy/Caddyfile:
menu.example.de {
reverse_proxy localhost:3000
}
sudo systemctl reload caddy
Caddy holt beim ersten Aufruf automatisch ein Zertifikat und leitet
danach HTTP→HTTPS um. Alternative: nginx + certbot, falls bereits ein
nginx-Setup existiert (Standard-proxy_pass-Konfiguration auf
http://127.0.0.1:3000, Zertifikat separat per certbot --nginx).
Danach in .env.local sicherstellen, dass APP_URL=https://menu.example.de
gesetzt ist (Schema https, keine Portangabe), und die App neu
starten, falls diese Variable nach Schritt 2 noch geändert wurde:
docker compose --env-file .env.local up -d
6. Danach
- Passkeys funktionieren erst mit korrekt gesetzter
APP_URL(RP-Domain wird direkt daraus abgeleitet) und über HTTPS (Passkeys verlangen eine sichere Origin,localhostausgenommen). - Weitere Kantinenmitarbeiter/Admins unter „Benutzer“ (nur als Admin sichtbar) anlegen.
- Essenszeiten, Gerichte, Allergene/Zusatzstoffe sind zu Beginn leer und werden im Kantinen-Bereich gepflegt.
- Logo/Favicon/Farben unter „Design“ (nur Admin) anpassen.
Fehlerbehebung
password authentication failed for user "postgres" in den App-Logs
Ursache: Das Docker-Volume der Datenbank wurde bereits mit einem
anderen Passwort initialisiert, als aktuell in .env.local steht (z. B.
weil docker compose up einmal ohne --env-file .env.local lief, bevor
das Passwort gesetzt war, oder LOCAL_PG_PASSWORD nachträglich geändert
wurde – PostgreSQL übernimmt das Passwort nur beim allerersten Start).
Fix, solange noch keine echten Daten in der Datenbank sind (bei einem frischen Deployment immer der Fall):
docker compose --env-file .env.local down
docker volume rm canteen-menu_pgdata
docker compose --env-file .env.local up -d --build
docker compose logs app --tail 30
Existieren bereits echte Daten, stattdessen das Passwort direkt im
laufenden Datenbank-Container an .env.local angleichen, statt das
Volume zu löschen:
docker compose exec db psql -U postgres -c "ALTER USER postgres WITH PASSWORD '<Wert aus LOCAL_PG_PASSWORD>';"
docker compose --env-file .env.local up -d
Error: connect ECONNREFUSED ...:5432 beim Start
Normal beim allerersten Start: Der App-Container versucht sich zu
verbinden, bevor PostgreSQL im db-Container fertig hochgefahren ist.
Dank restart: unless-stopped startet der App-Container automatisch
neu und verbindet sich, sobald die Datenbank bereit ist (kann ein paar
Sekunden bis wenige Minuten dauern). Mit docker compose logs app -f
verfolgen – taucht der Fehler dauerhaft (mehrere Minuten) weiter auf,
docker compose ps prüfen, ob der db-Container tatsächlich dauerhaft
läuft.
EACCES: permission denied, open '/app/ZUGANGSDATEN.md'
Die Datei wurde ohne Schreibrecht für alle angelegt (Schritt 2 oben,
chmod 666 fehlte). Der Admin-Benutzer wurde in diesem Fall trotzdem
in der Datenbank angelegt, nur die Zugangsdaten konnten nicht in die
Datei geschrieben werden – sie stehen dann stattdessen im Log
(docker compose logs app nach bootstrap-admin – Admin-Passwort:
durchsuchen). Für zukünftige Deployments zusätzlich die Berechtigung
korrigieren:
chmod 666 ZUGANGSDATEN.md
Falls das Passwort auch im Log nicht mehr auffindbar ist (z. B. weil Logs schon rotiert wurden), Admin-Konto neu anlegen lassen:
docker compose exec db psql -U postgres -d canteen_menu -c "delete from users;"
chmod 666 ZUGANGSDATEN.md
docker compose restart app
docker compose logs app --tail 20
cat ZUGANGSDATEN.md
Updates einspielen
git pull
docker compose --env-file .env.local up -d --build
Neue Datenbank-Migrationen (db/migrations/*.sql) werden beim Start
automatisch und idempotent angewendet, bestehende Daten bleiben
erhalten.
Backup
Zwei Docker-Volumes enthalten alle persistenten Daten:
canteen-menu_pgdata– die komplette PostgreSQL-Datenbankcanteen-menu_branding_data– hochgeladenes Logo/Favicon
docker run --rm -v canteen-menu_pgdata:/data -v "$PWD":/backup alpine \
tar czf /backup/pgdata-backup.tar.gz -C /data .
Für ein reines Datenbank-Backup eignet sich auch docker compose exec db pg_dump -U postgres canteen_menu > backup.sql.