Ejecutar Potato en Docker
Sirve un proyecto de Potato desde la imagen de Docker publicada, conserva las anotaciones en tu disco y define la clave de administración y las variables de entorno que lee el contenedor.
Esta página explica cómo ejecutar un proyecto de anotación de Potato en un contenedor, desde el primer docker run hasta el traslado de un estudio en marcha a una nueva versión. Está escrita para investigadores y administradores de laboratorio que sirven un estudio desde su propia máquina o servidor y quieren usar Potato sin una instalación de Python en el host. Potato publica la imagen en GitHub Container Registry como ghcr.io/davidjurgens/potato, y la imagen contiene el servidor y sus dependencias y nada más. Como la carpeta de tu proyecto se monta en /app cuando arranca el contenedor, una sola imagen sirve para cualquier tarea.
potato deploy ejecuta la misma imagen en los hosts en la nube que el comando configura, y elige la etiqueta que corresponde a la versión de Potato que tienes instalada. Ejecutar Potato en local o en la nube cubre esa vía, y Configuración de producción cubre gunicorn, un proxy inverso y TLS en un servidor que gestionas tú.
Obtener la imagen
Instalar con pip las dependencias numpy, pandas, scipy y scikit-learn de Potato agota la memoria en una máquina de 1 GB y tarda entre cinco y diez minutos en una de 2 GB. La imagen ya incluye esas dependencias instaladas:
docker pull ghcr.io/davidjurgens/potato:2.14.0La imagen tiene dos variantes, cada una con una etiqueta móvil, una etiqueta por versión y una etiqueta por commit:
| Etiqueta | Contenido |
|---|---|
latest, 2.14.0 | Las dependencias básicas de Potato, los clientes de copia de seguridad (huggingface_hub y boto3) y Authlib para los inicios de sesión SSO |
latest-all, 2.14.0-all | Todo lo anterior, más el extra all con los SDK de IA que usa ai_support, la ingesta de PDF y DOCX, la exportación a parquet y el controlador de MySQL |
sha-<short>, sha-<short>-all | Un commit de la rama master |
Ambas variantes se compilan para linux/amd64 y linux/arm64, y la compilación arm64 se ejecuta de forma nativa en Apple Silicon. Ninguna incluye el extra vision ni sus ruedas de torch de varios gigabytes. La segmentación en el navegador funciona sin el extra, porque ONNX Runtime Web viene dentro del paquete de Potato.
Fija una etiqueta de versión en cualquier estudio que vaya a durar más de un día. La etiqueta latest se recompila desde la rama master en cada cambio, y dos docker pull con una semana de diferencia pueden descargar código distinto. Una etiqueta de versión como 2.14.0 se publica una sola vez al lanzar la versión y no se recompila después.
Compilar la imagen tú mismo
Compila desde un clon del repositorio cuando necesites un extra que ninguna de las dos variantes publicadas incluye. El argumento de compilación POTATO_EXTRAS acepta cualquier extra que nombre el setup.py de Potato, o varios separados por comas:
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 .El extra saml, para el inicio de sesión con SAML, no forma parte de all. El Dockerfile instala la biblioteca libxmlsec1, contra la que se compila el extra, solo cuando POTATO_EXTRAS nombra saml. Ejecuta una compilación local por su etiqueta, como potato:all, donde esta página nombra ghcr.io/davidjurgens/potato:2.14.0.
Ejecutar un proyecto
Una carpeta de proyecto contiene config.yaml y todos los archivos que nombra la configuración. Los ejemplos de esta página usan una tarea de sentimiento de tres ítems con la siguiente estructura:
myproject/
├── config.yaml
└── data/
└── items.jsonlLa configuración mantiene todas las rutas relativas a la carpeta del proyecto:
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]Cada línea de data/items.jsonl es un ítem:
{"id": "1", "text": "The battery lasts all day."}
{"id": "2", "text": "The screen cracked within a week."}
{"id": "3", "text": "It arrived on Tuesday."}Inicia el servidor desde la carpeta que contiene myproject:
docker run -d --name potato -p 8000:7860 \
-v "$PWD/myproject:/app" \
ghcr.io/davidjurgens/potato:2.14.0La tarea queda entonces en http://localhost:8000. El argumento -p 8000:7860 asigna el puerto 8000 de tu máquina al puerto 7860, en el que escucha el servidor dentro del contenedor. El argumento -v monta la carpeta del proyecto en /app. El lado del host tiene que ser una ruta como $PWD/myproject, porque Docker interpreta un nombre simple como myproject como el nombre de un volumen nuevo y vacío. docker logs -f potato sigue el registro del servidor, y docker stop potato detiene el servidor.
Un proyecto con varias configuraciones elige una con POTATO_CONFIG, una ruta relativa 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.0Ejecutar un estudio que dura semanas
Un estudio que dura semanas necesita una clave de firma de sesión que se mantenga igual entre reinicios y actualizaciones. Sin POTATO_SECRET_KEY, cada arranque del servidor elige una clave aleatoria nueva y cierra la sesión de todos los anotadores. Crea una sola vez un archivo de entorno, fuera de la carpeta del proyecto, y pasa el mismo archivo a cada 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 sección sobre la clave de administración explica la segunda línea del archivo. --restart unless-stopped vuelve a levantar el contenedor tras una caída o un reinicio del host.
Otros comandos de la imagen
Todo lo que sigue al nombre de la imagen sustituye al servidor. Comprueba una configuración con el validador que incluye la imagen, o abre una shell dentro del contenedor:
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 shPropiedad de archivos en Linux
El servidor de la imagen se ejecuta como uid 1000, y un montaje bind conserva la propiedad que la carpeta tiene en el host. En Linux, una carpeta que creaste pertenece a tu propio uid, y una carpeta creada por root pertenece a root. Si el propietario de la carpeta no es el uid 1000, el servidor no puede escribir anotaciones en ella. El contenedor comprueba la carpeta antes de iniciar el servidor y se detiene con ERROR: /app is not writable by uid 1000 en lugar de fallar a mitad del arranque. Cualquiera de los dos comandos siguientes resuelve el 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, las anotaciones y el archivo de clave de administración que escribe Potato también pertenecen a tu propia cuenta. Docker Desktop en macOS y Windows ignora la propiedad en los montajes bind, y un proyecto que funciona en un Mac aún puede fallar en un servidor Linux. POTATO_ALLOW_READONLY_APP=1 omite la comprobación. Potato sigue escribiendo las anotaciones, potato.log y sus bases de datos SQLite dentro del proyecto, así que define la variable solo cuando esas rutas se puedan escribir mediante montajes separados.
Algunos hosts inician todos los contenedores como root, sea cual sea la imagen. Cuando el contenedor arranca como root, cede /app al usuario potato de la imagen e inicia el servidor como ese usuario. Define POTATO_RUN_AS_ROOT=1 para mantener root.
Conservar las anotaciones
Potato escribe todo lo que recoge un estudio en la carpeta del proyecto. Las anotaciones van bajo output_annotation_dir, en un directorio por anotador que contiene el user_state.json de ese anotador. Dos bases de datos SQLite están junto a config.yaml. project.sqlite guarda las notas, el libro de códigos, los casos, el índice de búsqueda y el flujo de revisión, y datasets.sqlite guarda los conjuntos de datos de evaluación y sus versiones. Ninguna de las dos bases de datos puede reconstruirse a partir de los archivos de datos. Con un montaje bind, todos estos archivos están en tu disco y siguen allí cuando se elimina el contenedor.
output_annotation_dir tiene que resolverse dentro de la carpeta del proyecto. Una ruta como /data/annotations detiene el servidor al arrancar con el mensaje output_annotation_dir: Path '/data/annotations' resolves to '/data/annotations' which is outside the project directory '/app'. Mantén una ruta relativa como annotation_output/. El volumen /data de la imagen también queda fuera del proyecto, y Potato no escribe nada allí.
Copia una base de datos fuera de un contenedor en ejecución con el comando de copia de seguridad de SQLite, nunca como un archivo simple. project.sqlite funciona en modo WAL mientras el servidor escribe en la base de datos, y una copia simple puede estar corrupta o carecer del trabajo reciente sin ningún aviso. La imagen incluye la herramienta sqlite3 para la instantánea:
docker exec potato sqlite3 /app/project.sqlite ".backup /tmp/snap.sqlite"
docker cp potato:/tmp/snap.sqlite ./project.sqliteLa mayoría de los hosts de contenedores gestionados borran el sistema de archivos del contenedor cuando este se reinicia. En un host así, mantén el proyecto en un almacenamiento persistente montado en /app, o añade un bloque backup a la configuración. El bloque copia las anotaciones y las instantáneas de las bases de datos a un bucket de S3 o a un conjunto de datos de Hugging Face según un calendario, y las restaura cuando el servidor arranca con un disco vacío. Todas las variantes de la imagen incluyen los clientes que necesita el bloque.
Variables de entorno
El contenedor lee estas variables al arrancar:
| Variable | Valor por defecto | Finalidad |
|---|---|---|
POTATO_CONFIG | config.yaml | Ruta de la configuración dentro del contenedor, relativa a /app |
PORT | 7860 | Puerto en el que escucha el servidor dentro del contenedor |
GUNICORN_WORKERS | 1 | Procesos worker; cualquier otro valor detiene el contenedor al arrancar |
GUNICORN_THREADS | 8 | Hilos del único worker, y número de solicitudes atendidas a la vez |
GUNICORN_TIMEOUT | 120 | Segundos que un worker puede permanecer en silencio antes de que gunicorn lo reinicie |
POTATO_SECRET_KEY | aleatoria en cada arranque | Firma las cookies de sesión; secret_key de la configuración tiene prioridad |
POTATO_ADMIN_API_KEY | archivo generado | La clave de API de administración; admin_api_key de la configuración tiene prioridad |
POTATO_GENERATED_TEMPLATES_DIR | se elige al arrancar | Dónde escribe Potato las plantillas de tarea que construye al arrancar |
POTATO_BUNDLE_URL | sin definir | URL de un .tar.gz del proyecto que se descarga en /app al arrancar |
POTATO_BUNDLE_SHA256 | sin definir | Suma de comprobación con la que debe coincidir el proyecto descargado |
POTATO_BUNDLE_TOKEN | sin definir | Se envía como Authorization: Bearer con la descarga |
POTATO_RUN_AS_ROOT | sin definir | 1 mantiene el servidor como root cuando el contenedor arranca como root |
POTATO_ALLOW_MULTIWORKER | sin definir | 1 permite que GUNICORN_WORKERS supere 1, lo que pierde anotaciones en un estudio real |
POTATO_ALLOW_READONLY_APP | sin definir | 1 omite la comprobación de arranque de que /app admite escritura |
Un contenedor que cambia PORT necesita el mismo puerto a la derecha de -p, como en -p 8000:9000 para PORT=9000.
Número de workers y de hilos
El contenedor se niega a arrancar con más de un worker de gunicorn. Potato guarda su conjunto de ítems, su cola de asignación y el estado de cada anotador en la memoria de un solo proceso. Un segundo worker tendría su propia copia de las tres cosas y asignaría ítems que el primer worker ya había asignado. Cada guardado reescribe user_state.json por completo, y el worker que guardara en último lugar descartaría las anotaciones del otro sin ningún error. Aumenta GUNICORN_THREADS para tener más solicitudes simultáneas, porque los hilos comparten una sola copia del estado.
Plantillas generadas
Potato construye una plantilla para la página de anotación de cada tarea al arrancar. Fuera de un contenedor, la plantilla va al paquete instalado siempre que el directorio del paquete admita escritura. La imagen instala Potato como root y ejecuta el servidor como uid 1000, de modo que el servidor no puede escribir en el directorio del paquete. Potato escribe entonces las plantillas en un directorio bajo /tmp que solo el usuario del servidor puede leer o escribir, y el registro de arranque nombra el directorio. Potato reconstruye las plantillas a partir de la configuración en cada arranque, y perder el directorio no cuesta nada. Define POTATO_GENERATED_TEMPLATES_DIR para poner las plantillas en otro lugar, como un montaje tmpfs.
Descargar el proyecto al arrancar
Los hosts que solo ejecutan una imagen, como Render, Fly, Railway y ECS, no tienen ninguna carpeta que montar. Con POTATO_BUNDLE_URL definida, el contenedor descarga un .tar.gz del proyecto, comprueba la descarga con POTATO_BUNDLE_SHA256 y descomprime el proyecto en /app antes de iniciar el servidor. El contenedor escribe la suma de comprobación en /app/.potato-bundle-sha, y un reinicio con un disco persistente omite una descarga cuya suma de comprobación coincide. En un paquete creado por potato deploy, una nueva versión del proyecto nunca reemplaza la salida de anotaciones ni las bases de datos SQLite que ya están en el disco. potato deploy up define estas variables por ti.
La clave de administración en un contenedor
El panel de administración en /admin y la API de administración usan una única clave compartida, enviada como encabezado X-API-Key o escrita en la página de inicio de sesión de administración. Potato lee la clave primero de la clave de configuración de nivel superior admin_api_key y después de la variable de entorno POTATO_ADMIN_API_KEY. En un contenedor, la variable de entorno mantiene la clave fuera de config.yaml y de cualquier repositorio que contenga el proyecto. El archivo potato.env de la sección sobre estudios largos define la variable, y la clave de administración es entonces la que contiene ese archivo:
grep POTATO_ADMIN_API_KEY potato.envCuando ni la configuración ni el entorno definen una clave, Potato genera una la primera vez que alguien abre /admin o llama a un endpoint de administración. Potato escribe la clave generada en admin_api_key.txt, en el directorio de la tarea. En el contenedor, el directorio de la tarea es /app, la carpeta del proyecto montada. El archivo solo puede leerlo el usuario del servidor, y Potato lee el mismo archivo tras un reinicio. El registro del servidor nombra el archivo y nunca imprime la clave. Lee la clave a través del contenedor:
docker exec potato cat /app/admin_api_key.txtEn un host Linux en el que cediste la carpeta del proyecto al uid 1000, tu propia cuenta no puede leer admin_api_key.txt sin sudo. Un contenedor iniciado con --user "$(id -u):$(id -g)" escribe el archivo con tu propia cuenta. Consulta la página Panel de administración para ver qué abre la clave.
Modo de depuración en un contenedor
Mantén debug: false en cualquier contenedor que sirva un estudio. Con debug: true en la configuración, Potato cambia el comportamiento de dos funciones:
- Páginas de administración sin clave. El panel de administración y la API de administración omiten la clave en una solicitud procedente de una dirección de loopback que no lleva ningún encabezado
X-Forwarded-For,X-Real-IPniForwarded. En la red puente predeterminada de Docker, una solicitud al puerto publicado llega al contenedor desde la dirección del puente y sigue necesitando la clave. Con--network host, una solicitud desde la máquina host es una solicitud de loopback y no necesita clave. Un proxy inverso en el mismo espacio de nombres de red que no añade ningún encabezado de reenvío hace que todas las solicitudes que el proxy transmite parezcan locales. - Cuenta de depuración compartida. Con
debug_phasetambién definido, todos los visitantes inician sesión como una única cuenta compartida,debug_user, y saltan a esa fase. La cuenta no es de administración, pero todos los visitantes comparten las respuestas de la cuenta.
El servidor registra una advertencia al arrancar siempre que debug está activado. Un comando ejecutado dentro del contenedor con docker exec es una solicitud de loopback. En modo de depuración, docker exec potato curl -s http://127.0.0.1:7860/admin/health devuelve por tanto el informe de estado de administración sin clave.
Comprobaciones de estado
GET /health no necesita autenticación e informa solo de si el servidor está activo. La ruta devuelve {"status": "ok"} cuando el servidor está listo y el estado 503 mientras el servidor aún carga datos, y un balanceador de carga o un orquestador puede consultar la ruta como su comprobación de disponibilidad. El HEALTHCHECK propio de la imagen consulta /health cada 30 segundos y concede un periodo de arranque de 90 segundos para las tareas grandes, y docker ps muestra el resultado. GET /admin/health informa del nombre de la tarea y del estado de los gestores de ítems y de anotadores, y la ruta necesita la clave de administración en el encabezado X-API-Key.
Docker Compose
El repositorio de Potato no incluye ningún archivo Compose. Configuración de producción muestra un archivo Compose que ejecuta esta imagen detrás de nginx, con la carpeta del proyecto montada en /app y las claves de sesión y de administración leídas del entorno.
Actualizar a una versión nueva
Actualizar Potato sustituye el contenedor y deja en su sitio la carpeta del proyecto, con todas las anotaciones y bases de datos. Antes de cambiar, descarga la nueva versión, comprueba la configuración con el validador de la nueva versión y haz una instantánea de la base de datos del proyecto. Sustituye 2.14.0 por la versión a la que te pasas:
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.sqliteDespués, sustituye el contenedor en ejecución por un contenedor con la nueva etiqueta, con el mismo montaje y el mismo archivo de entorno:
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.0Como potato.env mantiene igual POTATO_SECRET_KEY, los anotadores siguen con la sesión iniciada tras el cambio, y la clave de administración no cambia.
Solución de problemas
ERROR: config file not found: config.yaml. El proyecto no se montó donde buscaba el servidor. El error lista el contenido de /app para compararlo con tu argumento -v. Un listado vacío suele indicar un nombre simple como -v myproject:/app. Docker interpreta un nombre simple como un volumen nuevo y vacío, no como tu carpeta.
ERROR: /app is not writable by uid 1000. El servidor no puede escribir en la carpeta del proyecto. Cede la carpeta al uid 1000 o ejecuta el contenedor con --user. Propiedad de archivos en Linux da ambos comandos.
output_annotation_dir ... is outside the project directory '/app'. La configuración apunta la salida de anotaciones fuera de la carpeta montada. Usa una ruta relativa como annotation_output/.
Todos pierden la sesión tras un reinicio. No hay ninguna clave de firma definida, y el servidor eligió una clave aleatoria nueva al arrancar. Define POTATO_SECRET_KEY en un archivo de entorno y pasa el archivo a cada docker run.
El contenedor termina con un error que nombra GUNICORN_WORKERS. El número de workers es superior a 1. Define GUNICORN_WORKERS=1, o elimina la variable, y aumenta GUNICORN_THREADS en su lugar.
La página de inicio de sesión de administración rechaza todas las claves. El admin_api_key de la configuración tiene prioridad sobre POTATO_ADMIN_API_KEY, y ambos tienen prioridad sobre admin_api_key.txt. Comprueba primero la configuración y después docker exec potato printenv POTATO_ADMIN_API_KEY.
Más información
- Ejecutar Potato en local o en la nube - destinos de
potato deployque ejecutan esta imagen por ti - Configuración de producción - gunicorn, nginx, TLS, sesiones y copias de seguridad en tu propio servidor
- Proxy inverso - servir Potato bajo un prefijo de ruta de URL
- Panel de administración - lo que abre la clave de administración
Para más detalles de implementación, consulta la documentación fuente.