Multi-Stage-Build mit standalone-Output, non-root Nutzer node (UID 1000) und Healthcheck auf /api/health. Der Entrypoint prueft zuerst, ob /data beschreibbar ist, und sagt bei einem Rechteproblem genau, was zu tun ist; danach laufen die Migrationen und erst dann startet die App. Der Migrations-Runner wird beim Build mit esbuild zu einer einzelnen CJS-Datei gebuendelt. Damit braucht das Runtime-Image weder drizzle-kit noch tsx - nur das native better-sqlite3, das ohnehin dabei ist. update.sh sichert data und .env, holt den neuen Stand, baut, startet und wartet auf den Healthcheck. Wird der Container nicht gesund, rollt es Code und Container auf den vorherigen Commit zurueck und nennt den Pfad der Sicherung. .gitattributes erzwingt LF: eine entrypoint.sh mit CRLF scheitert im Container mit "no such file or directory" auf der Shebang-Zeile. Verifiziert: Image gebaut (358 MB), Container healthy, Migration laeuft, Registrierung des ersten Nutzers funktioniert, sharp arbeitet unter musl. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
211 lines
6.4 KiB
Markdown
211 lines
6.4 KiB
Markdown
# Bricktrack – Schnellstart
|
||
|
||
Von null auf laufende Instanz. Zeitbedarf: etwa 15 Minuten, davon 10 für den
|
||
ersten Docker-Build.
|
||
|
||
**Voraussetzungen:** Ein Linux-Host (z. B. eine LXC oder VM auf Proxmox) mit
|
||
Docker und dem Compose-Plugin, ein Reverse Proxy mit gültigem TLS-Zertifikat
|
||
und eine Subdomain, die auf den Proxy zeigt.
|
||
|
||
> **Warum HTTPS Pflicht ist:** Passkeys (WebAuthn) funktionieren
|
||
> ausschließlich über HTTPS und sind fest an den Hostnamen gebunden. Ohne
|
||
> Zertifikat bleibt nur die Anmeldung per Passwort. Wird die Domain später
|
||
> geändert, sind **alle** Passkeys ungültig und müssen neu angelegt werden.
|
||
|
||
---
|
||
|
||
## 1. Projekt holen
|
||
|
||
```bash
|
||
sudo mkdir -p /opt/bricktrack
|
||
sudo chown "$USER" /opt/bricktrack
|
||
git clone https://git.wilde-server.de/florian.wilde/bricktrack.git /opt/bricktrack
|
||
cd /opt/bricktrack
|
||
```
|
||
|
||
## 2. Konfiguration anlegen
|
||
|
||
```bash
|
||
cp .env.example .env
|
||
```
|
||
|
||
Jetzt `.env` öffnen und zwei Werte setzen:
|
||
|
||
```bash
|
||
# Die Adresse, unter der du die App im Browser aufrufst.
|
||
BASE_URL=https://bricktrack.example.de
|
||
|
||
# Signiergeheimnis - hiermit erzeugen:
|
||
# openssl rand -base64 48
|
||
BETTER_AUTH_SECRET=<dein-erzeugter-wert>
|
||
```
|
||
|
||
Alles andere kann so bleiben. `DATA_DIR=/data` ist der Pfad **im Container**
|
||
und wird nicht angefasst.
|
||
|
||
## 3. Datenverzeichnis vorbereiten
|
||
|
||
Der Container läuft aus Sicherheitsgründen nicht als root, sondern als Nutzer
|
||
mit der UID 1000. Das gemountete Verzeichnis muss ihm gehören:
|
||
|
||
```bash
|
||
mkdir -p ./data
|
||
sudo chown -R 1000:1000 ./data
|
||
```
|
||
|
||
Diesen Schritt vergisst man gern – der Container sagt dir dann aber beim Start
|
||
genau das.
|
||
|
||
## 4. Starten
|
||
|
||
```bash
|
||
docker compose up -d --build
|
||
```
|
||
|
||
Der erste Build dauert einige Minuten. Danach:
|
||
|
||
```bash
|
||
docker compose ps # Status "healthy" abwarten
|
||
docker compose logs -f app # Log mitlesen, mit Strg+C beenden
|
||
curl -s http://127.0.0.1:3000/api/health
|
||
# {"status":"ok","time":"..."}
|
||
```
|
||
|
||
## 5. Reverse Proxy einrichten (Nginx Proxy Manager)
|
||
|
||
In NPM unter **Hosts → Proxy Hosts → Add Proxy Host**:
|
||
|
||
**Reiter „Details"**
|
||
|
||
| Feld | Wert |
|
||
|---|---|
|
||
| Domain Names | `bricktrack.example.de` |
|
||
| Scheme | `http` |
|
||
| Forward Hostname / IP | IP des Docker-Hosts, z. B. `192.168.1.50` |
|
||
| Forward Port | `3000` |
|
||
| Cache Assets | **aus** (Next.js setzt seine Cache-Header selbst) |
|
||
| Block Common Exploits | an |
|
||
| Websockets Support | **an** |
|
||
|
||
**Reiter „SSL"**
|
||
|
||
| Feld | Wert |
|
||
|---|---|
|
||
| SSL Certificate | Let's Encrypt anfordern oder vorhandenes wählen |
|
||
| Force SSL | **an** |
|
||
| HTTP/2 Support | an |
|
||
| HSTS Enabled | an (optional, aber sinnvoll) |
|
||
|
||
**Reiter „Advanced"** – hier hinein, sonst scheitern große Anleitungs-PDFs am
|
||
Standardlimit von 1 MB:
|
||
|
||
```nginx
|
||
# Anleitungs-PDFs koennen mehrere Megabyte gross sein.
|
||
client_max_body_size 64m;
|
||
|
||
# Uploads und PDF-Auslieferung duerfen etwas dauern.
|
||
proxy_read_timeout 300;
|
||
proxy_send_timeout 300;
|
||
|
||
# Die App leitet ihre oeffentliche Adresse und die Client-IP hieraus ab.
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
proxy_set_header X-Forwarded-Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
```
|
||
|
||
`client_max_body_size` muss mindestens so groß sein wie `MAX_UPLOAD_MB` in
|
||
deiner `.env` (Standard 25 MB); 64m gibt Luft.
|
||
|
||
> **Läuft NPM im selben Docker-Netz?** Dann in `docker-compose.yml` das
|
||
> Port-Mapping auf `- "3000:3000"` ändern oder ganz entfernen und beide
|
||
> Container in ein gemeinsames Netzwerk hängen. Als Forward Hostname trägst du
|
||
> dann `bricktrack` ein (den Container-Namen), Port `3000`.
|
||
>
|
||
> Die Voreinstellung `127.0.0.1:3000:3000` bindet den Port nur an localhost –
|
||
> die App ist dann ausschließlich über den Proxy erreichbar. Sitzt NPM auf
|
||
> einem **anderen** Host, muss das Mapping auf `3000:3000` geändert und der
|
||
> Zugriff per Firewall beschränkt werden.
|
||
|
||
## 6. Ersten Nutzer anlegen
|
||
|
||
`https://bricktrack.example.de` aufrufen. Die App leitet direkt auf die
|
||
Registrierung.
|
||
|
||
**Der erste registrierte Nutzer wird automatisch Administrator.** Danach ist
|
||
die Registrierung geschlossen – weitere Konten entstehen nur über einen
|
||
Einladungslink (ab Version 0.2.0).
|
||
|
||
Also: gleich beim ersten Aufruf selbst registrieren, bevor es jemand anderes
|
||
tut.
|
||
|
||
## 7. Passkey hinterlegen
|
||
|
||
Unter **Konto → Passkeys → Passkey hinzufügen**. Ab dann meldest du dich per
|
||
Fingerabdruck, Gesichtserkennung oder Geräte-PIN an. Für jedes Gerät ein
|
||
eigener Passkey – Handy, Tablet, Laptop.
|
||
|
||
## 8. Loslegen
|
||
|
||
1. **Verwaltung → Hersteller**: Lego, Playmobil, Cobi … anlegen.
|
||
2. **Verwaltung → Themen**: „City", „Technic", „Novelmore" …
|
||
3. **Verwaltung → Standorte**: „Regal Kinderzimmer", „Kiste Keller 3" …
|
||
4. **Katalog → Set anlegen**: Setnummer, Name, Bild.
|
||
5. Auf der Set-Seite **„Zur Sammlung hinzufügen"**, wenn das Set da ist.
|
||
6. **Katalog → Filter „Fehlt noch"**: Kachel lange drücken, mehrere auswählen,
|
||
**„Zur Wunschliste hinzufügen"**.
|
||
7. **Wunschlisten → Liste öffnen → Link kopieren** und verschicken.
|
||
|
||
Zum Ausprobieren gibt es Beispieldaten:
|
||
|
||
```bash
|
||
docker compose exec app node -e "process.exit(0)" # laeuft der Container?
|
||
docker compose exec app sh -c 'echo "Seed nur in der Entwicklung"'
|
||
```
|
||
|
||
Das Seed-Skript ist bewusst nicht im Produktions-Image. Zum Ausprobieren
|
||
lokal: `pnpm db:seed`.
|
||
|
||
---
|
||
|
||
## Sichern
|
||
|
||
Alles, was zählt, liegt in **einem** Verzeichnis:
|
||
|
||
```bash
|
||
docker compose stop app
|
||
tar czf bricktrack-$(date +%F).tar.gz data .env
|
||
docker compose start app
|
||
```
|
||
|
||
Zurückspielen:
|
||
|
||
```bash
|
||
docker compose down
|
||
tar xzf bricktrack-2026-09-06.tar.gz
|
||
sudo chown -R 1000:1000 ./data
|
||
docker compose up -d
|
||
```
|
||
|
||
## Aktualisieren
|
||
|
||
```bash
|
||
./update.sh
|
||
```
|
||
|
||
Das Skript sichert vorher automatisch, baut neu, wartet auf den Healthcheck
|
||
und rollt bei einem Fehlschlag zurück. Details in der [README](README.md).
|
||
|
||
---
|
||
|
||
## Wenn etwas klemmt
|
||
|
||
| Symptom | Ursache und Lösung |
|
||
|---|---|
|
||
| `FEHLER: In /data darf nicht geschrieben werden` | `sudo chown -R 1000:1000 ./data` |
|
||
| Container startet, aber `unhealthy` | `docker compose logs app` – meist eine fehlende Variable in `.env` |
|
||
| `Ungueltige Umgebungsvariablen: BETTER_AUTH_SECRET` | Das Geheimnis braucht mindestens 32 Zeichen |
|
||
| Passkey lässt sich nicht anlegen | Kein HTTPS, oder `BASE_URL` passt nicht zur aufgerufenen Domain |
|
||
| Passkeys funktionieren nach einem Domainwechsel nicht mehr | Erwartet – Passkeys sind an die Domain gebunden, neu anlegen |
|
||
| `413 Request Entity Too Large` beim PDF-Upload | `client_max_body_size` im Proxy erhöhen (Schritt 5) |
|
||
| Anmeldung meldet „Zu viele Versuche" | Rate-Limit: fünf Fehlversuche pro Minute, danach kurz warten |
|