Potato in Docker ausführen
Ein Potato-Projekt aus dem veröffentlichten Docker-Image bereitstellen, die Annotationen auf dem eigenen Datenträger behalten und den Admin-Schlüssel sowie die Umgebungsvariablen setzen, die der Container liest.
Diese Seite behandelt den Betrieb eines Potato-Annotationsprojekts in einem Container, vom ersten docker run bis zum Wechsel einer laufenden Studie auf ein neues Release. Sie richtet sich an Forschende und Laboradministratoren, die eine Studie von ihrem eigenen Rechner oder Server aus betreiben und Potato ohne Python-Installation auf dem Host nutzen möchten. Potato veröffentlicht das Image in der GitHub Container Registry als ghcr.io/davidjurgens/potato, und das Image enthält den Server und seine Abhängigkeiten und sonst nichts. Weil Ihr Projektordner beim Start des Containers unter /app eingebunden wird, bedient ein Image jede Aufgabe.
potato deploy führt dasselbe Image auf den Cloud-Hosts aus, die der Befehl einrichtet, und wählt dabei den Tag, der zu Ihrer installierten Potato-Version passt. Potato lokal oder in der Cloud ausführen behandelt diesen Weg, und Produktionseinrichtung behandelt gunicorn, einen Reverse Proxy und TLS auf einem Server, den Sie selbst verwalten.
Das Image beziehen
Die Installation der Abhängigkeiten numpy, pandas, scipy und scikit-learn von Potato mit pip geht auf einem Rechner mit 1 GB Speicher der Arbeitsspeicher aus und dauert auf einem mit 2 GB fünf bis zehn Minuten. Das Image liefert diese Abhängigkeiten bereits installiert mit:
docker pull ghcr.io/davidjurgens/potato:2.14.0Das Image gibt es in zwei Varianten, jeweils mit einem beweglichen Tag, einem Tag pro Release und einem Tag pro Commit:
| Tag | Inhalt |
|---|---|
latest, 2.14.0 | die Kernabhängigkeiten von Potato, die Backup-Clients (huggingface_hub und boto3) und Authlib für SSO-Logins |
latest-all, 2.14.0-all | alles Obige sowie das Extra all mit den KI-SDKs hinter ai_support, PDF- und DOCX-Import, Parquet-Export und dem MySQL-Treiber |
sha-<short>, sha-<short>-all | ein Commit des Master-Branches |
Beide Varianten werden für linux/amd64 und linux/arm64 gebaut, und der arm64-Build läuft auf Apple Silicon nativ. Keine der Varianten enthält das Extra vision mit seinen torch-Wheels von mehreren Gigabyte. Die Segmentierung im Browser funktioniert auch ohne das Extra, weil ONNX Runtime Web im Potato-Paket mitgeliefert wird.
Legen Sie sich für jede Studie, die länger als einen Tag läuft, auf einen Versions-Tag fest. Der Tag latest wird bei jeder Änderung neu aus dem Master-Branch gebaut, und zwei docker pull-Aufrufe im Abstand einer Woche können unterschiedlichen Code laden. Ein Versions-Tag wie 2.14.0 wird einmal zum Release-Zeitpunkt gesetzt und danach nicht mehr neu gebaut.
Das Image selbst bauen
Bauen Sie aus einem Klon des Repositorys, wenn Sie ein Extra brauchen, das keine der veröffentlichten Varianten enthält. Das Build-Argument POTATO_EXTRAS akzeptiert jedes in der setup.py von Potato genannte Extra oder mehrere, durch Kommas getrennt:
git clone https://github.com/davidjurgens/potato
cd potato
docker build -t potato .
docker build --build-arg POTATO_EXTRAS=all -t potato:all .
docker build --build-arg POTATO_EXTRAS=all,saml -t potato:all-saml .Das Extra saml für die SAML-Anmeldung gehört nicht zu all. Das Dockerfile installiert die Bibliothek libxmlsec1, gegen die das Extra kompiliert wird, nur dann, wenn POTATO_EXTRAS saml nennt. Starten Sie einen lokalen Build über seinen Tag, etwa potato:all, überall dort, wo diese Seite ghcr.io/davidjurgens/potato:2.14.0 nennt.
Ein Projekt ausführen
Ein Projektordner enthält config.yaml und jede Datei, die die Konfiguration nennt. Die Beispiele auf dieser Seite verwenden eine Sentiment-Aufgabe mit drei Items und folgendem Aufbau:
myproject/
├── config.yaml
└── data/
└── items.jsonlDie Konfiguration hält jeden Pfad relativ zum Projektordner:
annotation_task_name: "Sentiment pilot"
task_dir: .
data_files:
- data/items.jsonl
item_properties:
id_key: id
text_key: text
output_annotation_dir: annotation_output/
user_config:
allow_all_users: true
annotation_schemes:
- annotation_type: radio
name: sentiment
description: "What is the sentiment of this sentence?"
labels: [positive, neutral, negative]Jede Zeile von data/items.jsonl ist ein Item:
{"id": "1", "text": "The battery lasts all day."}
{"id": "2", "text": "The screen cracked within a week."}
{"id": "3", "text": "It arrived on Tuesday."}Starten Sie den Server aus dem Ordner, der myproject enthält:
docker run -d --name potato -p 8000:7860 \
-v "$PWD/myproject:/app" \
ghcr.io/davidjurgens/potato:2.14.0Die Aufgabe ist dann unter http://localhost:8000 erreichbar. Das Argument -p 8000:7860 bildet Port 8000 Ihres Rechners auf Port 7860 ab, auf dem der Server im Container lauscht. Das Argument -v bindet den Projektordner unter /app ein. Die Host-Seite muss ein Pfad wie $PWD/myproject sein, weil Docker einen bloßen Namen wie myproject als Namen eines neuen, leeren Volumes liest. docker logs -f potato verfolgt das Serverprotokoll, und docker stop potato hält den Server an.
Ein Projekt mit mehreren Konfigurationen wählt mit POTATO_CONFIG eine aus, als Pfad relativ zu /app:
docker run -d --name potato -p 8000:7860 \
-v "$PWD/myproject:/app" \
-e POTATO_CONFIG=configs/pilot.yaml \
ghcr.io/davidjurgens/potato:2.14.0Eine Studie ausführen, die Wochen dauert
Eine Studie, die Wochen läuft, braucht einen Signierschlüssel für Sitzungen, der über Neustarts und Updates hinweg gleich bleibt. Ohne POTATO_SECRET_KEY wählt jeder Start des Servers einen neuen zufälligen Schlüssel und meldet alle Annotatoren ab. Legen Sie einmal eine Env-Datei außerhalb des Projektordners an und übergeben Sie dieselbe Datei an jedes docker run:
printf 'POTATO_SECRET_KEY=%s\nPOTATO_ADMIN_API_KEY=%s\n' \
"$(openssl rand -hex 32)" "$(openssl rand -hex 32)" > potato.env
chmod 600 potato.env
docker run -d --name potato -p 8000:7860 \
-v "$PWD/myproject:/app" \
--env-file potato.env \
--restart unless-stopped \
ghcr.io/davidjurgens/potato:2.14.0Der Abschnitt zum Admin-Schlüssel erklärt die zweite Zeile der Datei. --restart unless-stopped bringt den Container nach einem Absturz oder einem Neustart des Hosts zurück.
Weitere Befehle im Image
Alles, was nach dem Image-Namen steht, ersetzt den Server. Prüfen Sie eine Konfiguration mit dem Validator, den das Image mitbringt, oder öffnen Sie eine Shell im Container:
docker run --rm -v "$PWD/myproject:/app" \
ghcr.io/davidjurgens/potato:2.14.0 potato validate config.yaml --strict
docker run --rm -it -v "$PWD/myproject:/app" \
ghcr.io/davidjurgens/potato:2.14.0 shDateibesitz unter Linux
Der Server im Image läuft als uid 1000, und ein Bind-Mount behält den Besitz, den der Ordner auf dem Host hat. Unter Linux gehört ein Ordner, den Sie angelegt haben, Ihrer eigenen uid, und ein von root angelegter Ordner gehört root. Ist der Besitzer des Ordners nicht uid 1000, kann der Server keine Annotationen in den Ordner schreiben. Der Container prüft den Ordner, bevor der Server startet, und bricht mit ERROR: /app is not writable by uid 1000 ab, statt mitten im Start zu scheitern. Jeder der folgenden Befehle behebt das Problem:
sudo chown -R 1000:1000 myproject # hand the folder to the container user
docker run --user "$(id -u):$(id -g)" ... # or run the container as yourselfMit --user gehören die Annotationen und die Admin-Schlüsseldatei, die Potato schreibt, ebenfalls Ihrem eigenen Konto. Docker Desktop unter macOS und Windows ignoriert den Besitz bei Bind-Mounts, und ein Projekt, das auf einem Mac läuft, kann auf einem Linux-Server dennoch scheitern. POTATO_ALLOW_READONLY_APP=1 überspringt die Prüfung. Potato schreibt Annotationen, potato.log und seine SQLite-Datenbanken weiterhin in das Projekt, setzen Sie die Variable also nur, wenn diese Pfade über getrennte Mounts beschreibbar sind.
Manche Hosts starten jeden Container als root, was auch immer das Image vorgibt. Wenn der Container als root startet, übergibt er /app an den Benutzer potato des Images und startet den Server als dieser Benutzer. Setzen Sie POTATO_RUN_AS_ROOT=1, um stattdessen root beizubehalten.
Die Annotationen behalten
Potato schreibt alles, was eine Studie sammelt, in den Projektordner. Annotationen liegen unter output_annotation_dir, in einem Verzeichnis pro Annotator, das die user_state.json dieses Annotators enthält. Zwei SQLite-Datenbanken liegen neben config.yaml. project.sqlite enthält Memos, das Codebuch, Fälle, den Suchindex und den Review-Workflow, und datasets.sqlite enthält Evaluationsdatensätze und deren Versionen. Keine der beiden Datenbanken lässt sich aus den Datendateien neu aufbauen. Mit einem Bind-Mount liegen alle diese Dateien auf Ihrem Datenträger und bleiben dort, wenn der Container entfernt wird.
output_annotation_dir muss innerhalb des Projektordners aufgelöst werden. Ein Pfad wie /data/annotations bricht den Start des Servers mit der Meldung output_annotation_dir: Path '/data/annotations' resolves to '/data/annotations' which is outside the project directory '/app' ab. Behalten Sie einen relativen Pfad wie annotation_output/ bei. Das /data-Volume des Images liegt ebenfalls außerhalb des Projekts, und Potato schreibt dort nichts.
Kopieren Sie eine Datenbank aus einem laufenden Container mit dem Backup-Befehl von SQLite, niemals als einfache Datei. project.sqlite läuft im WAL-Modus, während der Server in die Datenbank schreibt, und eine einfache Kopie kann beschädigt sein oder neuere Arbeit ohne jede Warnung auslassen. Das Image enthält das Werkzeug sqlite3 für den Snapshot:
docker exec potato sqlite3 /app/project.sqlite ".backup /tmp/snap.sqlite"
docker cp potato:/tmp/snap.sqlite ./project.sqliteDie meisten verwalteten Container-Hosts löschen das Dateisystem des Containers, wenn der Container neu startet. Halten Sie das Projekt auf einem solchen Host auf persistentem Speicher, der unter /app eingebunden ist, oder fügen Sie der Konfiguration einen backup-Block hinzu. Der Block kopiert die Annotationen und Datenbank-Snapshots nach Zeitplan in einen S3-Bucket oder ein Hugging-Face-Dataset und stellt sie wieder her, wenn der Server mit leerem Datenträger startet. Jede Variante des Images enthält die Clients, die der Block braucht.
Umgebungsvariablen
Der Container liest diese Variablen beim Start:
| Variable | Standard | Zweck |
|---|---|---|
POTATO_CONFIG | config.yaml | Pfad der Konfiguration im Container, relativ zu /app |
PORT | 7860 | Port, auf dem der Server im Container lauscht |
GUNICORN_WORKERS | 1 | Worker-Prozesse; jeder andere Wert bricht den Start des Containers ab |
GUNICORN_THREADS | 8 | Threads im einen Worker und die Zahl der gleichzeitig bedienten Anfragen |
GUNICORN_TIMEOUT | 120 | Sekunden, die ein Worker stumm bleiben darf, bevor gunicorn ihn neu startet |
POTATO_SECRET_KEY | bei jedem Start zufällig | Signiert Sitzungs-Cookies; secret_key in der Konfiguration hat Vorrang |
POTATO_ADMIN_API_KEY | generierte Datei | Der Admin-API-Schlüssel; admin_api_key in der Konfiguration hat Vorrang |
POTATO_GENERATED_TEMPLATES_DIR | beim Start gewählt | Wohin Potato die beim Start erzeugten Aufgaben-Templates schreibt |
POTATO_BUNDLE_URL | nicht gesetzt | URL eines .tar.gz des Projekts, das beim Start nach /app geladen wird |
POTATO_BUNDLE_SHA256 | nicht gesetzt | Prüfsumme, die das heruntergeladene Projekt erfüllen muss |
POTATO_BUNDLE_TOKEN | nicht gesetzt | Wird mit dem Download als Authorization: Bearer gesendet |
POTATO_RUN_AS_ROOT | nicht gesetzt | 1 lässt den Server als root weiterlaufen, wenn der Container als root startet |
POTATO_ALLOW_MULTIWORKER | nicht gesetzt | 1 erlaubt, dass GUNICORN_WORKERS 1 übersteigt, was in einer echten Studie Annotationen verliert |
POTATO_ALLOW_READONLY_APP | nicht gesetzt | 1 überspringt die Startprüfung, ob /app beschreibbar ist |
Ein Container, der PORT ändert, braucht denselben Port rechts von -p, etwa -p 8000:9000 für PORT=9000.
Anzahl der Worker und Threads
Der Container weigert sich, mit mehr als einem gunicorn-Worker zu starten. Potato hält seinen Item-Pool, seine Zuweisungswarteschlange und den Zustand jedes Annotators im Speicher eines Prozesses. Ein zweiter Worker hätte von allen dreien eine eigene Kopie und würde Items zuweisen, die der erste Worker bereits zugewiesen hat. Jedes Speichern schreibt user_state.json vollständig neu, und der Worker, der zuletzt speichert, würde die Annotationen des anderen ohne Fehlermeldung verwerfen. Erhöhen Sie stattdessen GUNICORN_THREADS für mehr gleichzeitige Anfragen, denn Threads teilen sich eine Kopie des Zustands.
Generierte Templates
Potato baut beim Start für die Annotationsseite jeder Aufgabe ein Template. Außerhalb eines Containers landet das Template im installierten Paket, sofern das Paketverzeichnis beschreibbar ist. Das Image installiert Potato als root und führt den Server als uid 1000 aus, sodass der Server nicht in das Paketverzeichnis schreiben kann. Potato schreibt die Templates stattdessen in ein Verzeichnis unter /tmp, das nur der Benutzer des Servers lesen oder beschreiben kann, und das Startprotokoll nennt das Verzeichnis. Potato baut die Templates bei jedem Start aus der Konfiguration neu, und der Verlust des Verzeichnisses kostet nichts. Setzen Sie POTATO_GENERATED_TEMPLATES_DIR, um die Templates an einem anderen Ort abzulegen, etwa auf einem tmpfs-Mount.
Das Projekt beim Start herunterladen
Hosts, die nur ein Image ausführen, etwa Render, Fly, Railway und ECS, haben keinen Ordner zum Einbinden. Ist POTATO_BUNDLE_URL gesetzt, lädt der Container ein .tar.gz des Projekts herunter, prüft den Download gegen POTATO_BUNDLE_SHA256 und entpackt das Projekt nach /app, bevor der Server startet. Der Container schreibt die Prüfsumme nach /app/.potato-bundle-sha, und ein Neustart auf einem persistenten Datenträger überspringt einen Download, dessen Prüfsumme übereinstimmt. In einem von potato deploy gebauten Bundle ersetzt eine neue Version des Projekts nie die Annotationsausgabe oder die SQLite-Datenbanken, die bereits auf dem Datenträger liegen. potato deploy up setzt diese Variablen für Sie.
Der Admin-Schlüssel in einem Container
Das Admin-Dashboard unter /admin und die Admin-API verwenden einen gemeinsamen Schlüssel, der als Header X-API-Key gesendet oder auf der Admin-Anmeldeseite eingegeben wird. Potato liest den Schlüssel zuerst aus dem Top-Level-Konfigurationsschlüssel admin_api_key, dann aus der Umgebungsvariablen POTATO_ADMIN_API_KEY. In einem Container hält die Umgebungsvariable den Schlüssel aus config.yaml und aus jedem Repository heraus, das das Projekt enthält. Die Datei potato.env aus dem Abschnitt zu langen Studien setzt die Variable, und der Admin-Schlüssel ist dann das, was diese Datei enthält:
grep POTATO_ADMIN_API_KEY potato.envSetzen weder die Konfiguration noch die Umgebung einen Schlüssel, erzeugt Potato einen Schlüssel, sobald jemand zum ersten Mal /admin öffnet oder einen Admin-Endpunkt aufruft. Potato schreibt den erzeugten Schlüssel in admin_api_key.txt im Aufgabenverzeichnis. Im Container ist das Aufgabenverzeichnis /app, der eingebundene Projektordner. Die Datei ist nur für den Benutzer des Servers lesbar, und Potato liest nach einem Neustart dieselbe Datei. Das Serverprotokoll nennt die Datei und gibt den Schlüssel nie aus. Lesen Sie den Schlüssel über den Container:
docker exec potato cat /app/admin_api_key.txtAuf einem Linux-Host, auf dem Sie den Projektordner an uid 1000 übergeben haben, kann Ihr eigenes Konto admin_api_key.txt nicht ohne sudo lesen. Ein Container, der mit --user "$(id -u):$(id -g)" gestartet wird, schreibt die Datei stattdessen als Ihr eigenes Konto. Welche Bereiche der Schlüssel öffnet, steht auf der Seite Admin-Dashboard.
Debug-Modus in einem Container
Lassen Sie debug: false in jedem Container, der eine Studie bedient. Mit debug: true in der Konfiguration ändert Potato das Verhalten von zwei Funktionen:
- Admin-Seiten ohne Schlüssel. Das Admin-Dashboard und die Admin-API verzichten auf den Schlüssel bei einer Anfrage von einer Loopback-Adresse, die keinen Header
X-Forwarded-For,X-Real-IPoderForwardedträgt. Im Standard-Bridge-Netzwerk von Docker erreicht eine Anfrage an den veröffentlichten Port den Container von der Adresse der Bridge aus und braucht weiterhin den Schlüssel. Mit--network hostist eine Anfrage vom Host-Rechner eine Loopback-Anfrage und braucht keinen Schlüssel. Ein Reverse Proxy im selben Netzwerk-Namespace, der keinen Weiterleitungs-Header hinzufügt, lässt jede Anfrage, die der Proxy weiterreicht, lokal aussehen. - Gemeinsames Debug-Konto. Ist zusätzlich
debug_phasegesetzt, wird jeder Besucher als ein gemeinsames Konto,debug_user, angemeldet und in diese Phase übersprungen. Das Konto ist kein Admin, aber alle Besucher teilen sich die Antworten des Kontos.
Der Server protokolliert beim Start eine Warnung, sobald debug aktiv ist. Ein mit docker exec im Container ausgeführter Befehl ist eine Loopback-Anfrage. Im Debug-Modus liefert docker exec potato curl -s http://127.0.0.1:7860/admin/health daher den Admin-Gesundheitsbericht ohne Schlüssel.
Health-Checks
GET /health braucht keine Authentifizierung und meldet nur, ob der Server läuft. Die Route liefert {"status": "ok"}, sobald der Server bereit ist, und den Status 503, solange der Server noch Daten lädt, und ein Load Balancer oder ein Orchestrator kann die Route als Bereitschaftsprüfung abfragen. Der HEALTHCHECK des Images fragt /health alle 30 Sekunden ab und gewährt für große Aufgaben eine Startphase von 90 Sekunden, und docker ps zeigt das Ergebnis an. GET /admin/health meldet den Namen der Aufgabe und den Zustand der Item- und Annotator-Manager, und die Route braucht den Admin-Schlüssel im Header X-API-Key.
Docker Compose
Das Potato-Repository liefert keine Compose-Datei mit. Produktionseinrichtung zeigt eine Compose-Datei, die dieses Image hinter nginx ausführt, mit dem unter /app eingebundenen Projektordner und den aus der Umgebung gelesenen Sitzungs- und Admin-Schlüsseln.
Auf eine neue Version aktualisieren
Ein Update von Potato ersetzt den Container und lässt den Projektordner mit allen Annotationen und Datenbanken an Ort und Stelle. Ziehen Sie vor dem Wechsel das neue Release, prüfen Sie die Konfiguration mit dem Validator des neuen Releases und erstellen Sie einen Snapshot der Projektdatenbank. Ersetzen Sie 2.14.0 durch das Release, auf das Sie wechseln:
docker pull ghcr.io/davidjurgens/potato:2.14.0
docker run --rm -v "$PWD/myproject:/app" \
ghcr.io/davidjurgens/potato:2.14.0 potato validate config.yaml --strict
docker exec potato sqlite3 /app/project.sqlite ".backup /tmp/snap.sqlite"
docker cp potato:/tmp/snap.sqlite ./project-before-update.sqliteErsetzen Sie dann den laufenden Container durch einen Container mit dem neuen Tag, mit demselben Mount und derselben Env-Datei:
docker stop potato
docker rm potato
docker run -d --name potato -p 8000:7860 \
-v "$PWD/myproject:/app" \
--env-file potato.env \
--restart unless-stopped \
ghcr.io/davidjurgens/potato:2.14.0Weil potato.env POTATO_SECRET_KEY unverändert hält, bleiben die Annotatoren über den Wechsel hinweg angemeldet, und der Admin-Schlüssel ändert sich nicht.
Fehlerbehebung
ERROR: config file not found: config.yaml. Das Projekt wurde nicht dort eingebunden, wo der Server suchte. Der Fehler listet zum Vergleich mit Ihrem -v-Argument den Inhalt von /app auf. Eine leere Liste deutet meist auf einen bloßen Namen wie -v myproject:/app hin. Docker liest einen bloßen Namen als neues, leeres Volume und nicht als Ihren Ordner.
ERROR: /app is not writable by uid 1000. Der Server kann nicht in den Projektordner schreiben. Übergeben Sie den Ordner an uid 1000 oder führen Sie den Container mit --user aus. Dateibesitz unter Linux nennt beide Befehle.
output_annotation_dir ... is outside the project directory '/app'. Die Konfiguration legt die Annotationsausgabe außerhalb des eingebundenen Ordners ab. Verwenden Sie einen relativen Pfad wie annotation_output/.
Nach einem Neustart sind alle abgemeldet. Es ist kein Signierschlüssel gesetzt, und der Server hat beim Start einen neuen zufälligen Schlüssel gewählt. Setzen Sie POTATO_SECRET_KEY in einer Env-Datei und übergeben Sie die Datei an jedes docker run.
Der Container bricht mit einem Fehler ab, der GUNICORN_WORKERS nennt. Die Worker-Anzahl liegt über 1. Setzen Sie GUNICORN_WORKERS=1 oder entfernen Sie die Variable, und erhöhen Sie stattdessen GUNICORN_THREADS.
Die Admin-Anmeldeseite weist jeden Schlüssel ab. admin_api_key aus der Konfiguration hat Vorrang vor POTATO_ADMIN_API_KEY, und beide haben Vorrang vor admin_api_key.txt. Prüfen Sie zuerst die Konfiguration, dann docker exec potato printenv POTATO_ADMIN_API_KEY.
Weiterführende Literatur
- Potato lokal oder in der Cloud ausführen -
potato deploy-Ziele, die dieses Image für Sie ausführen - Produktionseinrichtung - gunicorn, nginx, TLS, Sitzungen und Backups auf Ihrem eigenen Server
- Reverse-Proxy (URL-Pfadpräfix) - Potato unter einem URL-Pfadpräfix bereitstellen
- Admin-Dashboard - was der Admin-Schlüssel öffnet
Implementierungsdetails finden Sie in der Quelldokumentation.