Without it, the subdomain deployment (app.<domain>/admin.<domain> sharing a login with the bare domain) would silently get host-only cookies since the value never reached the container's environment. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ScanReceipts — Receipt Scanner App
Next.js 15 App Router App zum Scannen/Digitalisieren von Belegen (KI-Extraktion, Review-UI, Export nach XLSX/CSV/JSON). Lokal läuft die App ohne Datenbank (Local-First IndexedDB im Browser); PostgreSQL wird für Accounts, Abos (Stripe) und den Admin-Bereich gebraucht.
Diese Datei ist der Einstiegspunkt. Tiefere Details stehen in:
| Thema | Datei |
|---|---|
| Auth / SMTP / Google Login im Detail | AUTH_SETUP_GUIDE.md |
| Stripe / Preise / Webhooks im Detail | STRIPE_SETUP_GUIDE.md |
| nginx / Security-Header / blockierte Pfade | docs/SECURITY_DEPLOYMENT.md |
| Architektur, Code-Layout, Feature-Liste | PROJECT.md |
| Was beim Security-Hardening gemacht wurde | SECURITY_HARDENING.md, SECURITY_VERIFICATION.md |
1. Voraussetzungen
- Node.js 20+ und npm
- Docker Desktop (für Postgres lokal und/oder für den Produktions-Build)
- Ein
.env.local(siehe unten) — ohneDATABASE_URLläuft die App im Local-Only-Modus (kein Login/Stripe, aber Scannen funktioniert)
Achtung Port-Konflikt: Auf dieser Maschine laufen ~10 Docker-Stacks. Port
5432ist von einem anderen Projekt belegt — dieses Projekt nutzt bewusst5436auf dem Host (siehedocker-compose.yml). Beim Anlegen vonDATABASE_URLin.env.localimmerlocalhost:5436verwenden, nicht5432.
2. Schnellstart (lokale Entwicklung)
npm install
cp .env.example .env.local
.env.local öffnen und mindestens OPENROUTER_API_KEY (oder einen anderen
Vision-Key) eintragen, damit das Scannen funktioniert. Alles andere
(Datenbank, Stripe, Google-Login, SMTP) ist optional für den reinen
Scan-Workflow.
Nur Postgres per Docker starten (App läuft weiter direkt mit npm run dev,
nicht im Container):
docker compose up -d postgres
Postgres ist danach unter localhost:5436 erreichbar. In .env.local:
DATABASE_URL=postgresql://receipt_user:receipt_secure_password@localhost:5436/receipt_scanner
Dev-Server starten:
npm run dev
→ http://localhost:3000. Der erste Request, der die DB anfasst, legt das Schema automatisch an (siehe Abschnitt 3.1) — ein manueller Migrationsschritt ist für lokale Entwicklung nicht zwingend nötig.
Server neustarten: einfach Strg+C und npm run dev erneut ausführen.
Bei hängenden Prozessen/kaputtem .next-Cache (kommt auf dieser Maschine
gelegentlich vor):
# .next Ordner löschen und neu starten
rm -rf .next
npm run dev
3. Datenbank
Zwei Rollen sind vorgesehen (siehe PROJECT.md für die volle Begründung):
receipt_user— Owner-Rolle, macht Migrationen/Schema-Änderungen (DDL).receipt_app— Runtime-Rolle, nur SELECT/INSERT/UPDATE/DELETE, kein DDL. Wird vonscripts/db-permissions.sqlangelegt. In Produktion sollte die App mit dieser eingeschränkten Rolle laufen (APP_DATABASE_URL), Migrationen laufen weiter über die Owner-Rolle (DATABASE_URL).
3.1 Datenbank neu erstellen / Schema anlegen
Drei gleichwertige Wege, je nach Situation:
- Automatisch (einfachster Weg): Einfach die App starten
(
npm run devoder den Docker-Container). Beim ersten DB-Zugriff prüftsrc/lib/db/init.ts, ob das Schema existiert, und legt es sonst automatisch an (über diedrizzle-Migrationsdateien, mit idempotentem SQL-Fallback, falls die Migrationsdateien zur Laufzeit fehlen — z. B. im Docker-Image). - Explizit per Drizzle Kit (empfohlen während der Entwicklung, wenn man
das Schema in
src/lib/schema/db.tsändert):npm run db:generate # neue Migrationsdatei aus Schema-Änderungen erzeugen npm run db:push # Schema direkt in die DB pushen (ohne Migrationsdatei) npm run db:studio # Drizzle Studio GUI zum Browsen der Daten - Frisches Docker-Volume:
docker compose up postgresauf einem noch nie benutzten Volume führt automatischscripts/db-permissions.sqlaus (Postgres-Init-Mechanismus) — legt dabei direkt auch die Runtime-Rollereceipt_appan.
3.2 Datenbank zurücksetzen (alle Daten löschen)
Docker-Setup (empfohlen für lokal):
docker compose down
docker volume rm receiptscannerapp_postgres_data # Name ggf. mit `docker volume ls` prüfen
docker compose up -d postgres
Das erzeugt ein komplett frisches Volume; Schema + Berechtigungen werden beim nächsten App-Start automatisch wieder angelegt (siehe 3.1).
Ohne Docker / bestehende DB behalten, nur Tabellen leeren: per psql oder
Drizzle Studio (npm run db:studio) die Tabellen manuell leeren — es gibt
aktuell kein db:reset-Script, das gezielt nur Daten löscht.
3.3 Berechtigungen (Least-Privilege-Rolle) anwenden/prüfen
Nötig, wenn eine bereits bestehende Datenbank auf die eingeschränkte Runtime-Rolle nachgerüstet werden soll (bei einem frischen Docker-Volume passiert das automatisch, siehe 3.1 Punkt 3):
node scripts/apply-db-permissions.mjs # legt/aktualisiert die Rolle receipt_app
node scripts/verify-db-permissions.mjs # prüft: kein Superuser, kein DDL, DML funktioniert
Beide Scripts lesen DATABASE_URL aus .env.local (Owner-Verbindung nötig).
3.4 Admin-User anlegen
node scripts/create-admin.ts
Nutzt ADMIN_EMAILS (erste Adresse in der kommagetrennten Liste) und
optional ADMIN_PASSWORD (Default: fiesta — in Produktion immer setzen)
aus .env.local. Legt den User an oder setzt Passwort + E-Mail-Verifizierung
zurück, falls er schon existiert.
4. Website starten / neustarten
4.1 Lokal (ohne Docker)
npm run dev # Entwicklung, mit Turbopack + Hot Reload
npm run build # Produktions-Build
npm run start # Produktions-Server aus dem Build starten (Port 3000)
4.2 Kompletter Stack per Docker Compose (App + Postgres)
docker compose up -d --build # baut das Image und startet App + DB im Hintergrund
docker compose logs -f app # Logs der App live mitlesen
docker compose ps # Status/Healthchecks prüfen
Neustarten (z. B. nach .env-Änderungen, die kein Rebuild brauchen):
docker compose restart app
Neu bauen und neustarten (nach Code-Änderungen):
docker compose up -d --build app
Alles stoppen (Daten bleiben im Volume erhalten):
docker compose down
docker-compose.yml liest seine Werte aus der Shell-Umgebung bzw. einer
.env-Datei im Repo-Root (nicht zu verwechseln mit .env.local, das nur
Next.js selbst liest) — für den Docker-Weg müssen die Variablen also in .env
stehen, siehe .env.example als Vorlage.
5. Live-Server / Deployment-Checkliste
5.1 Environment-Variablen
Alle Variablen sind in .env.example dokumentiert (mit Erklärung, wofür sie sind). Kurzfassung, was auf dem Live-Server zusätzlich zur lokalen Config wichtig ist:
NODE_ENV=productionNEXT_PUBLIC_APP_URL→ musshttps://sein (die App ist HTTPS-only, erzwingt HSTS). Wird zur Build-Zeit in den Client-Bundle eingebacken, also als Docker-Build-Arg mitgeben (machtdocker-compose.ymlautomatisch überbuild.args).COOKIE_DOMAINsetzen, falls Dashboard/Admin auf Subdomains laufen (z. B..scan-receipts.app), sonst leer lassen.CORS_ORIGINSnur setzen, wenn wirklich Cross-Origin-API-Zugriff gebraucht wird (nie*).SMTP_*sind in Produktion Pflicht — ohne SMTP schlägt die Registrierung bewusst fehl, statt einen unbestätigbaren Account anzulegen.GOOGLE_CLIENT_ID/SECRETnur wenn Google-Login aktiv sein soll (Redirect-URI in der Google Cloud Console muss exakt<NEXT_PUBLIC_APP_URL>/api/auth/google/callbacksein).STRIPE_*siehe STRIPE_SETUP_GUIDE.md — Webhook-Secret ist pflicht, sonst werden keine Lizenzen vergeben.ADMIN_EMAILS+ starkesADMIN_PASSWORDfürscripts/create-admin.ts.APP_DATABASE_URL+APP_DATABASE_PASSWORDsetzen und die App damit laufen lassen (nicht mit der Owner-DATABASE_URL) — siehe Abschnitt 3.
Es liegt bereits ein .env.deployment als Referenz im Repo-Root — das ist die
Vorlage mit den auf dem Live-Server tatsächlich benötigten Keys (Werte selbst
niemals ins Git-Repo committen).
5.2 Erstmaliges Deployment
# auf dem Server, im Projektverzeichnis
docker compose up -d --build
- Postgres startet zuerst, Healthcheck muss grün sein, bevor die App startet
(
depends_on: condition: service_healthy, ist indocker-compose.ymlschon konfiguriert). - Auf einem frischen Volume werden Runtime-Rolle + Berechtigungen automatisch angelegt (Abschnitt 3.1 Punkt 3).
- Schema wird beim ersten Request automatisch angelegt (Abschnitt 3.1 Punkt 1).
- Admin-User anlegen:
node scripts/create-admin.ts(mit Produktions-.env.localbzw. den Server-Umgebungsvariablen). - nginx davorschalten — die App selbst bindet nur intern an
127.0.0.1:3000, TLS/HSTS/Security-Header kommen von nginx:nginx.conf.examplenach/etc/nginx/conf.d/receipt-scanner.confkopieren, Platzhalter (example.com, Zertifikatspfade) ersetzen.- TLS-Zertifikat besorgen (z. B. Let's Encrypt).
nginx -t && systemctl reload nginx.- Details/Begründung: docs/SECURITY_DEPLOYMENT.md.
- Stripe-Webhook-Endpoint auf
https://<domain>/api/stripe/webhookim Stripe-Dashboard eintragen (siehe STRIPE_SETUP_GUIDE.md Abschnitt 5).
5.3 Redeploy nach Code-Änderungen
git pull
docker compose up -d --build app
Datenbank-Migrationen laufen automatisch beim ersten Request nach dem Neustart
mit (Abschnitt 3.1). Bei Schema-Änderungen, die Drizzle nicht automatisch
sauber migrieren kann, vorher lokal gegen eine Kopie der Prod-DB mit
npm run db:generate eine Migration erzeugen und testen.
5.4 Neustart ohne Rebuild
docker compose restart app
5.5 Logs & Healthcheck prüfen
docker compose logs -f app
docker compose logs -f postgres
curl -f http://127.0.0.1:3000/api/auth/providers # das nutzt auch der Docker-Healthcheck
5.6 Backup
Das Postgres-Datenverzeichnis liegt im benannten Volume postgres_data. Dump
vor jedem größeren Deployment:
docker exec scanreceipts_postgres pg_dump -U receipt_user receipt_scanner > backup_$(date +%Y%m%d_%H%M%S).sql
6. Nützliche Scripts (Auswahl)
| Command | Zweck |
|---|---|
npm run dev / build / start |
Dev-Server / Produktions-Build / Produktions-Server |
npm run db:generate / db:push / db:studio |
Drizzle-Migrationen erzeugen / pushen / GUI |
npm run test |
E2E-Testsuite (tests/e2e/runner.ts) |
npm run test:auth |
Auth/DB-Integrationstest |
node scripts/create-admin.ts |
Admin-User anlegen/zurücksetzen |
node scripts/apply-db-permissions.mjs |
Least-Privilege-Runtime-Rolle anlegen/aktualisieren |
node scripts/verify-db-permissions.mjs |
Rechte-Setup verifizieren |
node scripts/verify_sensitive_paths.mjs |
Prüft, dass sensible Pfade (.env, .git, …) blockiert sind |
node scripts/verify_cors.mjs |
CORS-Policy verifizieren |
node scripts/verify_rate_limit.mjs |
Rate-Limiting (Passwort-Reset u. a.) verifizieren |
Weitere verify_*-Scripts in scripts/ decken einzelne Security-Hardening-
Maßnahmen ab (siehe SECURITY_HARDENING.md für den
Kontext, wofür jedes Script steht).
7. Troubleshooting
- "DATABASE_URL is not set" →
.env.localfehlt oder wurde nicht geladen; Next.js liest.env.localautomatisch, Node-Scripts (drizzle-kit, diescripts/*.mjs) laden es teils manuell — prüfen, ob die Datei existiert. - Port
5432schon belegt → dieses Projekt läuft absichtlich auf5436, siehe Kasten oben in Abschnitt 1. .next-Cache kaputt / komische Build-Fehler →rm -rf .nextund neu starten (ist auf dieser Maschine schon mehrfach vorgekommen, siehe die.next-corrupt-*-Ordner im Repo-Root).- Google-Login-Button erscheint nicht →
GOOGLE_CLIENT_ID/SECRETfehlen; der Button rendert nur, wenn beide gesetzt sind. - Signup schlägt mit
mail_failedfehl → in Produktion (NODE_ENV=production) ist SMTP Pflicht, es gibt keinen Dev-Fallback.