Eseguire Potato in Docker
Servi un progetto Potato dall'immagine Docker pubblicata, conserva le annotazioni sul tuo disco e imposta la chiave di amministrazione e le variabili d'ambiente che il container legge.
Questa pagina spiega come eseguire un progetto di annotazione Potato in un container, dal primo docker run fino al passaggio di uno studio in corso a una nuova versione. La pagina è scritta per ricercatori e amministratori di laboratorio che servono uno studio dalla propria macchina o da un server e vogliono usare Potato senza un'installazione di Python sull'host. Potato pubblica l'immagine su GitHub Container Registry come ghcr.io/davidjurgens/potato, e l'immagine contiene il server e le sue dipendenze e nient'altro. Poiché la cartella del progetto viene montata su /app all'avvio del container, un'unica immagine serve ogni task.
potato deploy esegue la stessa immagine sugli host cloud che il comando configura, e sceglie il tag che corrisponde alla versione di Potato installata. Eseguire Potato in locale o nel cloud tratta questo percorso, mentre Configurazione di produzione tratta gunicorn, un reverse proxy e TLS su un server che gestisci tu.
Ottenere l'immagine
Installare con pip le dipendenze numpy, pandas, scipy e scikit-learn di Potato esaurisce la memoria su una macchina da 1 GB e richiede da cinque a dieci minuti su una da 2 GB. L'immagine arriva con quelle dipendenze già installate:
docker pull ghcr.io/davidjurgens/potato:2.14.0L'immagine esiste in due varianti, ciascuna con un tag mobile, un tag per ogni release e un tag per ogni commit:
| Tag | Contenuto |
|---|---|
latest, 2.14.0 | Le dipendenze di base di Potato, i client di backup (huggingface_hub e boto3) e Authlib per gli accessi SSO |
latest-all, 2.14.0-all | Tutto quanto sopra, più l'extra all con gli SDK di intelligenza artificiale dietro ai_support, l'acquisizione di PDF e DOCX, l'esportazione in parquet e il driver MySQL |
sha-<short>, sha-<short>-all | Un singolo commit del branch master |
Entrambe le varianti sono costruite per linux/amd64 e linux/arm64, e la build arm64 gira in modo nativo su Apple Silicon. Nessuna delle due varianti include l'extra vision e i suoi wheel torch da vari gigabyte. La segmentazione nel browser funziona senza l'extra, perché ONNX Runtime Web è incluso nel pacchetto Potato.
Fissa un tag di versione per qualsiasi studio che resterà attivo più di un giorno. Il tag latest viene ricostruito dal branch master a ogni modifica, e due comandi docker pull a una settimana di distanza possono scaricare codice diverso. Un tag di versione come 2.14.0 viene pubblicato una sola volta al momento della release e non viene più ricostruito.
Costruire l'immagine da te
Costruisci l'immagine da un clone del repository quando ti serve un extra che nessuna delle due varianti pubblicate include. L'argomento di build POTATO_EXTRAS accetta qualsiasi extra nominato nel setup.py di Potato, oppure più extra separati da virgole:
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 .L'extra saml per l'accesso SAML non fa parte di all. Il Dockerfile installa la libreria libxmlsec1, contro cui l'extra viene compilato, solo quando POTATO_EXTRAS nomina saml. Esegui una build locale con il suo tag, per esempio potato:all, ovunque questa pagina nomini ghcr.io/davidjurgens/potato:2.14.0.
Eseguire un progetto
Una cartella di progetto contiene config.yaml e ogni file che la configurazione nomina. Gli esempi di questa pagina usano un task di sentiment con tre item e la struttura seguente:
myproject/
├── config.yaml
└── data/
└── items.jsonlLa configurazione tiene ogni percorso relativo alla cartella del progetto:
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]Ogni riga di data/items.jsonl è un 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."}Avvia il server dalla cartella che contiene myproject:
docker run -d --name potato -p 8000:7860 \
-v "$PWD/myproject:/app" \
ghcr.io/davidjurgens/potato:2.14.0Il task si trova quindi su http://localhost:8000. L'argomento -p 8000:7860 mappa la porta 8000 della tua macchina sulla porta 7860, su cui il server resta in ascolto dentro il container. L'argomento -v monta la cartella del progetto su /app. Il lato host deve essere un percorso come $PWD/myproject, perché Docker legge un nome semplice come myproject come il nome di un nuovo volume vuoto. docker logs -f potato segue il log del server, e docker stop potato lo ferma.
Un progetto con più configurazioni ne sceglie una con POTATO_CONFIG, un percorso relativo a /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.0Eseguire uno studio che dura settimane
Uno studio che dura settimane ha bisogno di una chiave di firma delle sessioni che resti uguale tra riavvii e aggiornamenti. Senza POTATO_SECRET_KEY, a ogni avvio il server sceglie una nuova chiave casuale e disconnette tutti gli annotatori. Crea una sola volta un file env fuori dalla cartella del progetto e passa lo stesso file a ogni 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.0La sezione sulla chiave di amministrazione spiega la seconda riga del file. --restart unless-stopped riporta in funzione il container dopo un crash o un riavvio dell'host.
Altri comandi nell'immagine
Qualsiasi cosa scritta dopo il nome dell'immagine sostituisce il server. Controlla una configurazione con il validatore incluso nell'immagine, oppure apri una shell dentro il 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 shProprietà dei file su Linux
Il server nell'immagine gira come uid 1000, e un bind mount mantiene la proprietà che la cartella ha sull'host. Su Linux, una cartella che hai creato tu appartiene al tuo uid, e una cartella creata da root appartiene a root. Se il proprietario della cartella non è l'uid 1000, il server non può scrivere le annotazioni nella cartella. Il container controlla la cartella prima di avviare il server e si ferma con ERROR: /app is not writable by uid 1000 invece di fallire a metà dell'avvio. Uno dei due comandi seguenti risolve il problema:
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 yourselfCon --user, anche le annotazioni e il file della chiave di amministrazione che Potato scrive appartengono al tuo account. Docker Desktop su macOS e Windows ignora la proprietà sui bind mount, e un progetto che funziona su un Mac può comunque fallire su un server Linux. POTATO_ALLOW_READONLY_APP=1 salta il controllo. Potato scrive comunque le annotazioni, potato.log e i suoi database SQLite dentro il progetto, quindi imposta la variabile solo quando quei percorsi sono scrivibili tramite mount separati.
Alcuni host avviano ogni container come root, qualunque cosa specifichi l'immagine. Quando il container parte come root, assegna /app all'utente potato dell'immagine e avvia il server come quell'utente. Imposta POTATO_RUN_AS_ROOT=1 per mantenere root.
Conservare le annotazioni
Potato scrive tutto ciò che uno studio raccoglie nella cartella del progetto. Le annotazioni vanno sotto output_annotation_dir, in una directory per annotatore che contiene il user_state.json di quell'annotatore. Due database SQLite stanno accanto a config.yaml. project.sqlite contiene memo, codebook, casi, l'indice di ricerca e il flusso di revisione, mentre datasets.sqlite contiene i dataset di valutazione e le loro versioni. Nessuno dei due database può essere ricostruito dai file di dati. Con un bind mount, tutti questi file sono sul tuo disco e vi restano quando il container viene rimosso.
output_annotation_dir deve risolversi dentro la cartella del progetto. Un percorso come /data/annotations ferma il server all'avvio con il messaggio output_annotation_dir: Path '/data/annotations' resolves to '/data/annotations' which is outside the project directory '/app'. Mantieni un percorso relativo come annotation_output/. Anche il volume /data dell'immagine si trova fuori dal progetto, e Potato non vi scrive nulla.
Copia un database fuori da un container in esecuzione con il comando di backup di SQLite, mai come semplice file. project.sqlite funziona in modalità WAL mentre il server scrive nel database, e una copia semplice può risultare corrotta o priva del lavoro recente senza alcun avviso. L'immagine include lo strumento sqlite3 per lo snapshot:
docker exec potato sqlite3 /app/project.sqlite ".backup /tmp/snap.sqlite"
docker cp potato:/tmp/snap.sqlite ./project.sqliteLa maggior parte degli host di container gestiti cancella il filesystem del container quando il container si riavvia. Su un host di questo tipo, tieni il progetto su uno storage persistente montato su /app, oppure aggiungi alla configurazione un blocco backup. Il blocco copia le annotazioni e gli snapshot dei database su un bucket S3 o su un dataset Hugging Face a intervalli regolari, e li ripristina quando il server parte con un disco vuoto. Ogni variante dell'immagine include i client di cui il blocco ha bisogno.
Variabili d'ambiente
Il container legge queste variabili all'avvio:
| Variabile | Predefinito | Scopo |
|---|---|---|
POTATO_CONFIG | config.yaml | Percorso della configurazione dentro il container, relativo a /app |
PORT | 7860 | Porta su cui il server resta in ascolto dentro il container |
GUNICORN_WORKERS | 1 | Processi worker; qualsiasi altro valore ferma il container all'avvio |
GUNICORN_THREADS | 8 | Thread nell'unico worker, e numero di richieste servite contemporaneamente |
GUNICORN_TIMEOUT | 120 | Secondi in cui un worker può restare in silenzio prima che gunicorn lo riavvii |
POTATO_SECRET_KEY | casuale a ogni avvio | Firma i cookie di sessione; secret_key nella configurazione ha la precedenza |
POTATO_ADMIN_API_KEY | file generato | La chiave API di amministrazione; admin_api_key nella configurazione ha la precedenza |
POTATO_GENERATED_TEMPLATES_DIR | scelta all'avvio | Dove Potato scrive i template del task che costruisce all'avvio |
POTATO_BUNDLE_URL | non impostata | URL di un .tar.gz del progetto da scaricare in /app all'avvio |
POTATO_BUNDLE_SHA256 | non impostata | Checksum a cui il progetto scaricato deve corrispondere |
POTATO_BUNDLE_TOKEN | non impostata | Inviato come Authorization: Bearer con il download |
POTATO_RUN_AS_ROOT | non impostata | 1 mantiene il server in esecuzione come root quando il container parte come root |
POTATO_ALLOW_MULTIWORKER | non impostata | 1 permette a GUNICORN_WORKERS di superare 1, il che in uno studio reale fa perdere annotazioni |
POTATO_ALLOW_READONLY_APP | non impostata | 1 salta il controllo all'avvio sulla scrivibilità di /app |
Un container che cambia PORT richiede la stessa porta a destra di -p, come in -p 8000:9000 per PORT=9000.
Numero di worker e di thread
Il container rifiuta di avviarsi con più di un worker gunicorn. Potato tiene il pool degli item, la coda di assegnazione e lo stato di ogni annotatore nella memoria di un solo processo. Un secondo worker avrebbe una propria copia di tutti e tre e assegnerebbe item che il primo worker aveva già assegnato. Ogni salvataggio riscrive user_state.json per intero, e il worker che salva per ultimo scarterebbe senza alcun errore le annotazioni dell'altro. Aumenta invece GUNICORN_THREADS per avere più richieste concorrenti, perché i thread condividono un'unica copia dello stato.
Template generati
All'avvio Potato costruisce un template per la pagina di annotazione di ogni task. Fuori da un container, il template viene scritto nel pacchetto installato ogni volta che la directory del pacchetto è scrivibile. L'immagine installa Potato come root ed esegue il server come uid 1000, quindi il server non può scrivere nella directory del pacchetto. Potato scrive invece i template in una directory sotto /tmp che solo l'utente del server può leggere o scrivere, e il log di avvio nomina la directory. Potato ricostruisce i template dalla configurazione a ogni avvio, e perdere la directory non costa nulla. Imposta POTATO_GENERATED_TEMPLATES_DIR per mettere i template altrove, per esempio su un montaggio tmpfs.
Scaricare il progetto all'avvio
Gli host che eseguono soltanto un'immagine, come Render, Fly, Railway ed ECS, non hanno una cartella da montare. Con POTATO_BUNDLE_URL impostata, il container scarica un .tar.gz del progetto, confronta il download con POTATO_BUNDLE_SHA256 e decomprime il progetto in /app prima di avviare il server. Il container scrive il checksum in /app/.potato-bundle-sha, e un riavvio su un disco persistente salta un download il cui checksum corrisponde. In un bundle costruito da potato deploy, una nuova versione del progetto non sostituisce mai l'output delle annotazioni né i database SQLite già presenti sul disco. potato deploy up imposta queste variabili al posto tuo.
La chiave di amministrazione in un container
La dashboard di amministrazione su /admin e l'API di amministrazione usano un'unica chiave condivisa, inviata come header X-API-Key oppure digitata nella pagina di accesso dell'amministratore. Potato legge la chiave prima dalla chiave di primo livello admin_api_key della configurazione, poi dalla variabile d'ambiente POTATO_ADMIN_API_KEY. In un container, la variabile d'ambiente tiene la chiave fuori da config.yaml e fuori da qualsiasi repository che contenga il progetto. Il file potato.env della sezione sugli studi lunghi imposta la variabile, e la chiave di amministrazione è allora quella che quel file contiene:
grep POTATO_ADMIN_API_KEY potato.envQuando né la configurazione né l'ambiente impostano una chiave, Potato ne genera una la prima volta che qualcuno apre /admin o chiama un endpoint di amministrazione. Potato scrive la chiave generata in admin_api_key.txt nella directory del task. Nel container, la directory del task è /app, la cartella del progetto montata. Il file è leggibile solo dall'utente del server, e Potato rilegge lo stesso file dopo un riavvio. Il log del server nomina il file e non stampa mai la chiave. Leggi la chiave attraverso il container:
docker exec potato cat /app/admin_api_key.txtSu un host Linux in cui hai assegnato la cartella del progetto all'uid 1000, il tuo account non può leggere admin_api_key.txt senza sudo. Un container avviato con --user "$(id -u):$(id -g)" scrive invece il file come il tuo account. La pagina Dashboard di Amministrazione spiega che cosa apre la chiave.
Modalità debug in un container
Mantieni debug: false in qualsiasi container che serve uno studio. Con debug: true nella configurazione, Potato cambia il comportamento di due funzioni:
- Pagine di amministrazione senza chiave. La dashboard di amministrazione e l'API di amministrazione saltano la chiave per una richiesta proveniente da un indirizzo di loopback che non porta alcun header
X-Forwarded-For,X-Real-IPoForwarded. Sulla rete bridge predefinita di Docker, una richiesta alla porta pubblicata raggiunge il container dall'indirizzo del bridge e richiede ancora la chiave. Con--network host, una richiesta dalla macchina host è una richiesta di loopback e non richiede alcuna chiave. Un reverse proxy nello stesso network namespace che non aggiunge alcun header di inoltro fa sembrare locale ogni richiesta che il proxy inoltra. - Account di debug condiviso. Se è impostato anche
debug_phase, ogni visitatore accede con un unico account condiviso,debug_user, e viene portato direttamente a quella fase. L'account non è un amministratore, ma tutti i visitatori condividono le risposte dell'account.
All'avvio il server registra un avviso ogni volta che debug è attivo. Un comando eseguito dentro il container con docker exec è una richiesta di loopback. In modalità debug, docker exec potato curl -s http://127.0.0.1:7860/admin/health restituisce quindi il report di salute di amministrazione senza una chiave.
Controlli di salute
GET /health non richiede autenticazione e riporta soltanto se il server è attivo. La route restituisce {"status": "ok"} quando il server è pronto e lo status 503 mentre il server sta ancora caricando i dati, e un load balancer o un orchestratore possono interrogare la route come controllo di prontezza. L'HEALTHCHECK dell'immagine interroga /health ogni 30 secondi e concede 90 secondi di avvio per i task grandi, e docker ps mostra il risultato. GET /admin/health riporta il nome del task e lo stato dei gestori di item e annotatori, e la route richiede la chiave di amministrazione nell'header X-API-Key.
Docker Compose
Il repository di Potato non include alcun file Compose. Configurazione di produzione mostra un file Compose che esegue questa immagine dietro nginx, con la cartella del progetto montata su /app e le chiavi di sessione e di amministrazione lette dall'ambiente.
Passare a una nuova versione
Aggiornare Potato sostituisce il container e lascia al suo posto la cartella del progetto, con ogni annotazione e ogni database. Prima di cambiare, scarica la nuova release, controlla la configurazione con il validatore della nuova release e fai uno snapshot del database del progetto. Sostituisci 2.14.0 con la release a cui stai passando:
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.sqlitePoi sostituisci il container in esecuzione con un container sul nuovo tag, usando lo stesso montaggio e lo stesso file env:
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.0Poiché potato.env mantiene invariata POTATO_SECRET_KEY, gli annotatori restano connessi durante il passaggio, e la chiave di amministrazione non cambia.
Risoluzione dei problemi
ERROR: config file not found: config.yaml. Il progetto non è stato montato dove il server lo cercava. L'errore elenca il contenuto di /app perché tu possa confrontarlo con il tuo argomento -v. Un elenco vuoto di solito indica un nome semplice come -v myproject:/app. Docker legge un nome semplice come un nuovo volume vuoto e non come la tua cartella.
ERROR: /app is not writable by uid 1000. Il server non può scrivere nella cartella del progetto. Assegna la cartella all'uid 1000 oppure esegui il container con --user. Proprietà dei file su Linux indica entrambi i comandi.
output_annotation_dir ... is outside the project directory '/app'. La configurazione punta l'output delle annotazioni fuori dalla cartella montata. Usa un percorso relativo come annotation_output/.
Tutti vengono disconnessi dopo un riavvio. Non è impostata alcuna chiave di firma, e il server ha scelto una nuova chiave casuale all'avvio. Imposta POTATO_SECRET_KEY in un file env e passa il file a ogni docker run.
Il container termina con un errore che nomina GUNICORN_WORKERS. Il numero di worker è superiore a 1. Imposta GUNICORN_WORKERS=1, oppure rimuovi la variabile, e aumenta invece GUNICORN_THREADS.
La pagina di accesso dell'amministratore rifiuta ogni chiave. admin_api_key della configurazione ha la precedenza su POTATO_ADMIN_API_KEY, ed entrambe hanno la precedenza su admin_api_key.txt. Controlla prima la configurazione, poi docker exec potato printenv POTATO_ADMIN_API_KEY.
Ulteriori letture
- Eseguire Potato in locale o nel cloud - i target di
potato deployche eseguono questa immagine al posto tuo - Configurazione di produzione - gunicorn, nginx, TLS, sessioni e backup sul tuo server
- Proxy inverso (prefisso del percorso URL) - servire Potato sotto un prefisso di percorso URL
- Dashboard di Amministrazione - che cosa apre la chiave di amministrazione
Per i dettagli di implementazione, consulta la documentazione sorgente.