Die App
Was sollte ich betreiben?
Was du betreiben kannst, von einer reinen Browser-Installation bis zum selbst gehosteten Haushalt
Diese Seite wurde maschinell aus der englischen Dokumentation übersetzt.
Vier Stufen. Jede fügt eine Funktion hinzu und ergänzt etwas, das du nun betreiben musst. Beginne ganz unten und halte an, sobald du hast, was du brauchst: Die meisten Personen bleiben bei Stufe 0 oder 1 stehen.
| Stufe | Du erhältst | Du betreibst | Compose-Datei |
|---|---|---|---|
| 0 | Teller-Tracking + KI-Scans | Nichts | nichts |
| 1 | Dasselbe, auf deinem eigenen Rechner | Einen zustandslosen Container | docker/compose.yml |
| 2 | Dein Tagebuch auf zwei Geräten und, auf einer verwalteten Instanz, eine geteilte KI-Abrechnung für einen Haushalt oder eine Organisation | + eine Datenbank und ein Geheimnis | docker/topologies/compose.core.yml |
| 3 | Scans auf deiner eigenen Hardware | + eine Modell-Laufzeitumgebung | docker/topologies/compose.inference.yml |
| 4 | Alles davon | Alles davon | docker/topologies/compose.full.yml |
Jede Compose-Datei ist Zeile für Zeile kommentiert; docker/topologies/README.md ist dieselbe Übersicht aus Compose-Sicht.
Auf jeder Stufe schlägt der App-Server für die Nutzenden außerdem Lebensmittelnamen in der Lebensmitteldatenbank von LowCarbCheck nach. Er sendet Namen, niemals ein Foto oder einen Tagebucheintrag. Ab Stufe 1 übernimmt das dein eigener Server: Wenn mehr als eine Person scannt, richte dafür einen kostenlosen Schlüssel ein. Siehe architecture.md.
Jeder Befehl unten läuft auch unter Podman als podman compose. Unter Ubuntu muss für diesen Unterbefehl das Paket podman-compose daneben installiert sein. Siehe podman.md.
Stufe 0: Nichts betreiben
Öffne eine bestehende Instanz wie https://openplate.lowcarbcheck.org und füge deinen eigenen Provider-Schlüssel unter Einstellungen → KI ein. Eine Registrierung gibt es nicht. Dein Tagebuch verbleibt im Speicher dieses Browsers und erreicht den Server der Instanz nie, sodass „die Instanz einer anderen Person zu nutzen“ diesem Betreiber weit weniger Einblick gibt, als die Formulierung vermuten lässt: siehe architecture.md. Die Namen der Lebensmittel, die du scannst oder suchst, passieren ihn jedoch auf dem Weg zur Lebensmitteldatenbank.
Auf dieser Stufe musst du nichts selbst betreiben. Der Browser behält das Tagebuch, der Browser ruft den Anbieter mit dem Schlüssel auf, den du eingefügt hast, und der Server der betreibenden Person liefert nur die Seite aus.
Quellcode des Diagramms
flowchart LR
host["Someone else's instance"] -->|"HTML and JS"| browser["Your browser"]
browser --- diary["Diary in this browser"]
browser -->|"photo and your key"| cloud["Cloud AI provider"]Du gewinnst: das gesamte Produkt in einer Minute für die reinen Kosten deiner eigenen KI-Nutzung. Du betreibst: nichts.
Der ehrliche Haken: Eine öffentliche Demo-Instanz garantiert keine Verfügbarkeit, und nichts dort wird für dich gesichert. Dein Tagebuch liegt in diesem Browser, und das Leeren der Browserdaten löscht es. Exportiere regelmäßig das JSON aus Profil → Deine Daten oder wechsle zu Stufe 1.
Stufe 1: Die App auf dem eigenen Rechner
mkdir -p ~/openplate && cd ~/openplate
curl -O https://raw.githubusercontent.com/LowCarbCheck/openplate/main/docker/compose.yml
docker compose -f compose.yml up -dÖffne es alshttp://localhost:3000auf diesem Rechner oder über HTTPS. Von einem anderen Gerät aus zeigthttp://<its address>:3000das Tagebuch an. Die App-Installation, die Offlinenutzung und die 1-Klick-Verbindung zu OpenRouter funktionieren dort nicht. Siehe self-hosting.md.
Stufe 1 ändert genau einen Baustein. Die Seite kommt aus einem Container, den du betreibst, und der Fotopfad ist exakt derselbe wie oben.
Quellcode des Diagramms
flowchart LR
app["openplate app, your box"] -->|"HTML and JS"| browser["Your browser"]
browser --- diary["Diary in this browser"]
browser -->|"photo and your key"| cloud["Cloud AI provider"]Du gewinnst: die App auf Hardware unter deiner Kontrolle, aktualisierbar nach deinem Zeitplan, ohne Abhängigkeit von der Instanz eines anderen. Du betreibst: einen Container. Keine Datenbank, kein .env-Schritt, kein zu erzeugendes Secret, nichts zu migrieren bei Updates. Wenn er stirbt, geht nichts verloren, weil er nichts speichert. Compose-Datei: docker/compose.yml.
Dies ist der empfohlene Endpunkt. Alles darunter bedeutet echten Betriebsaufwand.
Die vollständige Anleitung findest du in self-hosting.md. Sie behandelt HTTPS, das du für die PWA-Installation, die 1-Klick-Verbindung zu OpenRouter und die Anmeldung ab Stufe 2 benötigst.
Stufe 2: Sync hinzufügen
Du gewinnst: ein Tagebuch über deine Geräte hinweg. Am ehrlichsten lässt sich das als einer Person mit zwei Geräten beschreiben: ein Telefon und ein Laptop, die denselben Stand behalten. Familien sind der zweite Einsatzzweck, und ein schwächerer: Die Synchronisierung erfolgt pro Konto, zwei Personen mit einem gemeinsamen Konto teilen sich also ein Tagebuch, statt jeweils ein eigenes zu erhalten. Zwei Personen, die getrennte Tagebücher wollen, brauchen zwei Konten, oder einfach zwei Stufe-1-Geräte ganz ohne Synchronisierung.
Stufe 2 ergänzt einen zweiten Server und eine Datenbank dahinter. Jedes Gerät überträgt denselben verschlüsselten Blob und ruft den des anderen Geräts ab, und das Foto verlässt weiterhin jedes Gerät in Richtung des Anbieters.
Quellcode des Diagramms
flowchart LR
app["openplate app"] -->|"HTML and JS"| phone["Phone"]
app -->|"HTML and JS"| laptop["Laptop"]
phone -->|"ciphertext"| sync["openplate-core"]
laptop -->|"ciphertext"| sync
sync --> db[("Postgres")]
phone -->|"photo and your key"| cloud["Cloud AI provider"]
laptop -->|"photo and your key"| cloudDu betreibst: die App, einen Kontodienst und ein Postgres. Das ist ein echter Schritt: Ein Kontodienst hat eine Datenbank, die Backups lohnt, einen SERVER_SECRET, den man behalten will, und Nutzer, die sich aussperren können. Lies README von openplate-core, bevor du ihn ins öffentliche Internet stellst. Compose-Datei: docker/topologies/compose.core.yml.
mkdir -p ~/openplate && cd ~/openplate
curl -O https://raw.githubusercontent.com/LowCarbCheck/openplate/main/docker/topologies/compose.core.yml
echo "SERVER_SECRET=$(openssl rand -hex 32)" >> .env
echo "ADMIN_TOKEN=$(openssl rand -hex 32)" >> .env
echo "PUBLIC_APP_URL=https://openplate.example.com" >> .env
echo "PUBLIC_SYNC_URL=https://sync.example.com" >> .env
echo "TRUST_PROXY=1" >> .env # 1 behind one reverse proxy, 0 with none
docker compose -f compose.core.yml up -dKonten erfordern eine sichere Seite. Anmeldung, Registrierung und das Öffnen einer Einladung schlagen über einfacheshttp://<LAN address>fehl. Nutze HTTPS, mit einem Domainnamen oder in einem Heimnetzwerk ohne einen solchen, oderlocalhostüber einen ssh-Tunnel für einen Test. Niemand registriert sich von selbst. Du erstellst die erste Einladung auf dem Server mitADMIN_TOKEN, wie Das erste Konto erstellen zeigt. Ein Heimnetzwerk ohne Domainnamen erhält HTTPS über Caddy mit lokalem Zertifikat.
Der Dienst speichert jeden Eintrag als Geheimtext und empfängt dein Passwort nie. Er verwahrt den Wiederherstellungscode jedes Kontos, versiegelt unter einem eigenen Geheimnis, sodass ein vergessenes Passwort über einen Link zurückgesetzt wird (per E-Mail gesendet oder von dir auf einer Instanz ohne Mail ausgegeben) und das Tagebuch wieder da ist. Das bedeutet auch, dass der Betreiber des Dienstes ein Tagebuch darauf im Prinzip öffnen kann. sync.md nennt diesen Kompromiss vollständig, und die App tut das ebenfalls, bevor du das Einrichten des Syncs abschließt.
Der Server kann auch eine gemeinsame KI-Rechnung übernehmen, wenn du die Funktion aktivierst. Setze INSTANCE_MODE=managed, und die Instanz wird zu einer, die ein Administrator für einen Haushalt oder eine Organisation betreibt: Administratoren laden Personen über /admin (oder über die Admin-API) ein, legen ein tägliches Kontingent für jedes Konto fest, und jeder Scan im angemeldeten Zustand läuft über den eigenen KI-Proxy des Core-Servers: kein separater Dienst, kein separater Einladungslink. E-Mail ist optional: /admin zeigt die Einladung immer als Link an, den ein Administrator kopieren und versenden kann, ob per E-Mail verschickt oder nicht. Siehe configuration.md#managed-instances und family-setup.md dafür, wann es sich lohnt, dies anstelle von Sub-Keys des Anbieters zu aktivieren.
Auf einer verwalteten Instanz wickelt derselbe Server auch den Scan ab. Ein angemeldetes Mitglied sendet das Foto an den KI-Proxy, der Server rechnet es auf das Tageskontingent dieses Kontos an und leitet die Anfrage dorthin weiter, worauf die Administration ihn eingestellt hat.
Quellcode des Diagramms
flowchart LR
browser["Member's browser"] -->|"ciphertext"| sync["openplate-core, managed"]
browser -->|"photo"| sync
sync --- quota["Daily allowance per account"]
sync -->|"photo"| upstream["Cloud provider, or inference"]Lass Mitglieder einander einladen, aber halte die Zahl klein. Setze auf einer verwalteten Instanz MEMBER_INVITE_DAILY_AI_LIMIT und MEMBER_INVITE_ALLOWANCE_DAYS auf dem Core-Server. Dadurch kann ein normales Mitglied Personen einladen, ohne dich vorher zu fragen. Wenn du für den Anbieterschlüssel zahlst, setze auch MEMBER_INVITE_LIFETIME_CAP=2. Der Standardwert ist 5, was zu einer Instanz passt, auf der die KI-Kosten geteilt werden. Ein Wert von 2 reicht für Partner und Freunde, und er hält das Wachstum so langsam, dass du es beobachten kannst. Siehe configuration.md#member-invites.
Stufe 3: Selbst gehostete Inferenz ergänzen
Stufe 3 führt den Scan auf deiner Hardware aus. Der Browser sendet Fotos direkt an den Inference-Container. Deine Browser müssen die Adresse dieses Containers auflösen können. Das Modell erkennt jedes Lebensmittel und schätzt das Gewicht in Gramm. openplate-inference liest die Makronährstoffe aus deiner konfigurierten Lebensmittelquelle.
Quellcode des Diagramms
flowchart LR
app["openplate app"] -->|"HTML and JS"| browser["Your browser"]
browser --- diary["Diary in this browser"]
browser -->|"photo, browser reachable address"| inf["openplate-inference"]
inf --- weights["Model runtime and weights"]
inf --- usda["Configured food data, USDA by default"]Du gewinnst: lokale Teller-Scans ohne Cloud-KI-Konto, Gebühren pro Scan oder ausgehenden Fotoverkehr. Das Container-Image enthält standardmäßig einen Auszug aus USDA FoodData Central, sodass openplate-inference Makronährstoffe nachschlägt, statt sie zu erfinden. Du betreibst: eine Modell-Laufzeitumgebung und einige Gigabyte an Gewichten, plus alles Nötige, um den Endpunkt erreichbar zu machen aus deinen Browsern (das Foto wandert direkt vom Gerät zum Endpunkt, ein Compose-Hostname reicht hier also nicht aus). Compose-Datei: docker/topologies/compose.inference.yml.
mkdir -p ~/openplate && cd ~/openplate
curl -O https://raw.githubusercontent.com/LowCarbCheck/openplate/main/docker/topologies/compose.inference.yml
echo "INFERENCE_API_KEY=opk_$(openssl rand -hex 24)" >> .env
echo "PUBLIC_APP_URL=http://192.168.1.20:3000" >> .env
echo "PUBLIC_INFERENCE_URL=http://192.168.1.20:8300/v1" >> .env
echo "TRUST_PROXY=0" >> .env # 0 with no reverse proxy, 1 behind one
docker compose -f compose.inference.yml up -dErsetze 192.168.1.20 durch die Adresse deines Servers. Sobald die App hinter einem Reverse-Proxy über HTTPS läuft, muss die Inferenzadresse ebenfalls https:// nutzen, und TRUST_PROXY muss auf 1 gesetzt sein. self-hosting.md führt durch die Schritte.
Diese Stufe ist für zwei Arten von Personen gedacht:
- Die Hardware gehört dir. Ein GPU-Rechner oder ein ausreichend starker CPU-Rechner.
- Du betreibst bereits eine Modell-Laufzeitumgebung. Wenn du bereits llama.cpp, Ollama oder vLLM-on-GPU betreibst, setze
MODEL_PROFILE=externalundMODEL_RUNTIME_URL: openplate-inference lädt dann nichts herunter, startet kein zweites Modell und bindet einfach deine bestehende Installation ein. Prüfe vorher die Kompatibilitätsmatrix; der CPU-Build von vLLM kann dies nicht ausführen.
Ehrliche Hardware-Anforderungen. Das kleine Profil lite umfasst 2,0 GiB an Gewichten und verlangt Mindestens 8 moderne Kerne mit AVX2 und 4 GB freier Arbeitsspeicher auf einem reinen CPU-System; das größere Profil quality umfasst 5,8 GiB an Gewichten und setzt mindestens 5,8 GiB VRAM voraus. CPU-Scans dauern Sekunden bis Minuten, und der Durchsatz steigt durch Parallelität nicht: Plane die Kapazität so, als würde das System seriell arbeiten. Die gemessenen Werte pro Profil stehen in docs/hardware.md von openplate-inference. Lies das, bevor du Hardware kaufst.
Sobald es läuft, kannst du entweder jeder Person einen Schlüssel geben (Einstellungen → KI → OpenAI-kompatibel) oder DEFAULT_INFERENCE_BASE_URL und verwandte Variablen setzen, damit alle Besucher mit einem Fingertipp verbunden sind, allerdings mit der Einschränkung, dass DEFAULT_INFERENCE_API_KEY im Quelltext der Seite eingebettet und für jeden lesbar ist, der die App öffnen kann. Siehe configuration.md.
Der Core-Server und Inferenz sind unterschiedliche Ebenen
Man verwechselt sie leicht, und sie greifen ineinander.
- openplate-inference ist die Rechenschicht. Es beantwortet die Frage Was liegt auf diesem Teller?. Es enthält eine Modell-Laufzeitumgebung sowie Gewichte und verlangt entsprechende Hardware.
- openplate-core bildet auf einer verwalteten Instanz die Mandantenschicht. Es beantwortet die Frage Wer darf wie viel verbrauchen und wie entziehe ich diese Berechtigung wieder?. Es enthält kein Modell und leitet alles weiter.
Richte den KI-Proxy einer verwalteten Instanz auf deinen Inferenzrechner aus (UPSTREAM_BASE_URL von openplate-core, mit einem der API_KEYS des Inferenzdienstes als UPSTREAM_API_KEY), und du erhältst beides: Scans auf eigener Hardware, mit Kontingenten pro Konto davor. Richte ihn stattdessen auf einen Cloud-Anbieter aus, und du erhältst geteilte Ausgaben ganz ohne Hardware. So oder so überträgt derselbe Core-Server auch das Tagebuch: Synchronisierung und der KI-Proxy sind jetzt ein einziger Dienst, nicht zwei (architecture.md).
Stufe 4: Alles zusammen
Stufe 4 führt die beiden obigen Stufen zusammen. Hier kommt nichts Neues hinzu.
Quellcode des Diagramms
flowchart LR
app["openplate app"] -->|"HTML and JS"| phone["Phone"]
app -->|"HTML and JS"| laptop["Laptop"]
phone -->|"ciphertext"| sync["openplate-core"]
laptop -->|"ciphertext"| sync
sync --> db[("Postgres")]
phone -->|"photo"| inf["openplate-inference"]
laptop -->|"photo"| infDu gewinnst: Stufe 2 und Stufe 3 zusammen (dein Tagebuch auf jedem Gerät, auf deiner eigenen Hardware gescannt, ohne dass Daten an Dritte gehen). Du betreibst: das Ganze. App, Core-Server, Postgres, Modell-Runtime und im Browser erreichbare Adressen für zwei davon. Compose-Datei: docker/topologies/compose.full.yml. Der Header listet die Zeilen für .env auf: die von Stufe 2 und Stufe 3 zusammen.
Podman führt dies auf dieselbe Weise aus: podman compose -f compose.full.yml up -d.
Auf dieser Stufe gibt es fachlich nichts Neues zu lernen. Sie vereint die beiden obigen Stufen mit derselben SERVER_SECRET, derselben Sicherungspflicht und derselben Mindesthardware.
Rechnung teilen statt Server teilen
Wenn dein Grund für den Aufstieg auf dieser Leiter war, dass dein Haushalt mehr als einen KI-Schlüssel braucht, ist die erste Antwort gar keine Stufe. Das Problem lässt sich beim Anbieter lösen, mit Schlüsseln pro Person und Ausgabenlimits pro Person, ganz ohne zusätzliche Software. family-setup.md beschreibt die Schritte und nennt für den Fall, dass dein Anbieter keine gedeckelten Sub-Keys ausstellt, einen verwalteten Core-Server als Ausweichlösung (Stufe 2, mit INSTANCE_MODE=managed).