Executar o Potato no Docker
Sirva um projeto do Potato a partir da imagem Docker publicada, mantenha as anotações no seu disco e defina a chave de administrador e as variáveis de ambiente que o contêiner lê.
Esta página cobre a execução de um projeto de anotação do Potato em um contêiner, do primeiro docker run até a migração de um estudo em andamento para uma nova versão. A página é escrita para pesquisadores e administradores de laboratório que servem um estudo a partir da própria máquina ou de um servidor e querem o Potato sem uma instalação de Python no host. O Potato publica a imagem no GitHub Container Registry como ghcr.io/davidjurgens/potato, e a imagem contém o servidor e suas dependências e nada além disso. Como a pasta do seu projeto é montada em /app quando o contêiner inicia, uma única imagem serve qualquer tarefa.
potato deploy executa a mesma imagem nos hosts de nuvem que o comando configura e escolhe a tag que corresponde à versão do Potato instalada. Executar o Potato localmente ou na nuvem cobre esse caminho, e Configuração de Produção cobre gunicorn, um proxy reverso e TLS em um servidor que você administra.
Obtendo a imagem
Instalar com pip as dependências numpy, pandas, scipy e scikit-learn do Potato esgota a memória em uma máquina de 1 GB e leva de cinco a dez minutos em uma de 2 GB. A imagem já vem com essas dependências instaladas:
docker pull ghcr.io/davidjurgens/potato:2.14.0A imagem vem em duas variantes, cada uma com uma tag móvel, uma tag por versão e uma tag por commit:
| Tag | Conteúdo |
|---|---|
latest, 2.14.0 | As dependências principais do Potato, os clientes de backup (huggingface_hub e boto3) e o Authlib para logins SSO |
latest-all, 2.14.0-all | Tudo o que está acima, mais o extra all, com os SDKs de IA por trás de ai_support, a ingestão de PDF e DOCX, a exportação em parquet e o driver do MySQL |
sha-<short>, sha-<short>-all | Um commit do branch master |
As duas variantes são construídas para linux/amd64 e linux/arm64, e a build arm64 roda nativamente no Apple Silicon. Nenhuma das variantes inclui o extra vision e suas rodas torch de vários gigabytes. A segmentação no navegador funciona sem o extra, porque o ONNX Runtime Web vem dentro do pacote do Potato.
Fixe uma tag de versão em qualquer estudo que dure mais de um dia. A tag latest é reconstruída a partir do branch master a cada alteração, e dois comandos docker pull com uma semana de diferença podem baixar códigos diferentes. Uma tag de versão como 2.14.0 é publicada uma vez, no momento do lançamento, e não é reconstruída depois.
Construindo a imagem você mesmo
Construa a partir de um clone do repositório quando precisar de um extra que nenhuma das variantes publicadas inclui. O argumento de build POTATO_EXTRAS aceita qualquer extra nomeado no setup.py do Potato, ou vários separados por vírgulas:
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 .O extra saml, para login SAML, não faz parte de all. O Dockerfile instala a biblioteca libxmlsec1, com a qual o extra é compilado, somente quando POTATO_EXTRAS cita saml. Execute uma build local pela tag dela, como potato:all, onde esta página cita ghcr.io/davidjurgens/potato:2.14.0.
Executando um projeto
Uma pasta de projeto contém o config.yaml e todos os arquivos que a configuração cita. Os exemplos desta página usam uma tarefa de sentimento com três itens e a seguinte estrutura:
myproject/
├── config.yaml
└── data/
└── items.jsonlA configuração mantém todos os caminhos relativos à pasta do projeto:
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 linha de data/items.jsonl é um 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."}Inicie o servidor a partir da pasta que contém myproject:
docker run -d --name potato -p 8000:7860 \
-v "$PWD/myproject:/app" \
ghcr.io/davidjurgens/potato:2.14.0A tarefa fica então em http://localhost:8000. O argumento -p 8000:7860 associa a porta 8000 da sua máquina à porta 7860, onde o servidor escuta dentro do contêiner. O argumento -v monta a pasta do projeto em /app. O lado do host precisa ser um caminho como $PWD/myproject, porque o Docker lê um nome simples como myproject como o nome de um novo volume vazio. docker logs -f potato acompanha o log do servidor, e docker stop potato para o servidor.
Um projeto com várias configurações escolhe uma delas com POTATO_CONFIG, um caminho 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.0Executando um estudo que dura semanas
Um estudo que dura semanas precisa de uma chave de assinatura de sessão que continue a mesma entre reinicializações e atualizações. Sem POTATO_SECRET_KEY, cada inicialização do servidor escolhe uma nova chave aleatória e desconecta todos os anotadores. Crie um arquivo de ambiente uma vez, fora da pasta do projeto, e passe o mesmo arquivo 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.0A seção sobre a chave de administrador explica a segunda linha do arquivo. --restart unless-stopped traz o contêiner de volta depois de uma falha ou de uma reinicialização do host.
Outros comandos na imagem
Tudo o que vem depois do nome da imagem substitui o servidor. Verifique uma configuração com o validador que a imagem traz, ou abra um shell dentro do contêiner:
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 shPropriedade de arquivos no Linux
O servidor na imagem roda como uid 1000, e uma montagem de pasta mantém a propriedade que a pasta tem no host. No Linux, uma pasta que você criou pertence ao seu próprio uid, e uma pasta criada pelo root pertence ao root. A menos que o dono da pasta seja o uid 1000, o servidor não consegue gravar anotações nela. O contêiner verifica a pasta antes de o servidor iniciar e para com ERROR: /app is not writable by uid 1000 em vez de falhar no meio da inicialização. Qualquer um dos comandos abaixo resolve o 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 yourselfCom --user, as anotações e o arquivo da chave de administrador que o Potato grava também pertencem à sua própria conta. O Docker Desktop no macOS e no Windows ignora a propriedade nas montagens de pasta, e um projeto que roda em um Mac ainda pode falhar em um servidor Linux. POTATO_ALLOW_READONLY_APP=1 pula a verificação. O Potato ainda grava as anotações, o potato.log e seus bancos de dados SQLite dentro do projeto, então defina a variável somente quando esses caminhos forem graváveis por montagens separadas.
Alguns hosts iniciam todo contêiner como root, seja qual for a especificação da imagem. Quando o contêiner inicia como root, ele entrega /app ao usuário potato da imagem e inicia o servidor como esse usuário. Defina POTATO_RUN_AS_ROOT=1 para manter o root.
Mantendo as anotações
O Potato grava tudo o que um estudo coleta na pasta do projeto. As anotações ficam em output_annotation_dir, em um diretório por anotador que contém o user_state.json desse anotador. Dois bancos de dados SQLite ficam ao lado de config.yaml. O project.sqlite guarda memorandos, o livro de códigos, casos, o índice de busca e o fluxo de revisão, e o datasets.sqlite guarda conjuntos de dados de avaliação e suas versões. Nenhum dos bancos pode ser reconstruído a partir dos arquivos de dados. Com uma montagem de pasta, todos esses arquivos ficam no seu disco e permanecem lá quando o contêiner é removido.
output_annotation_dir precisa resolver para dentro da pasta do projeto. Um caminho como /data/annotations interrompe o servidor na inicialização com a mensagem output_annotation_dir: Path '/data/annotations' resolves to '/data/annotations' which is outside the project directory '/app'. Mantenha um caminho relativo como annotation_output/. O volume /data da imagem também fica fora do projeto, e o Potato não grava nada nele.
Copie um banco de dados para fora de um contêiner em execução com o comando de backup do SQLite, nunca como um arquivo comum. O project.sqlite roda em modo WAL enquanto o servidor grava no banco, e uma cópia comum pode ficar corrompida ou perder trabalho recente sem nenhum aviso. A imagem inclui a ferramenta sqlite3 para o snapshot:
docker exec potato sqlite3 /app/project.sqlite ".backup /tmp/snap.sqlite"
docker cp potato:/tmp/snap.sqlite ./project.sqliteA maioria dos hosts de contêiner gerenciados apaga o sistema de arquivos do contêiner quando ele reinicia. Em um host assim, mantenha o projeto em um armazenamento persistente montado em /app, ou adicione um bloco backup à configuração. O bloco copia as anotações e os snapshots dos bancos para um bucket S3 ou um conjunto de dados do Hugging Face em intervalos regulares e os restaura quando o servidor inicia com um disco vazio. Todas as variantes da imagem incluem os clientes de que o bloco precisa.
Variáveis de ambiente
O contêiner lê estas variáveis na inicialização:
| Variável | Padrão | Finalidade |
|---|---|---|
POTATO_CONFIG | config.yaml | Caminho da configuração dentro do contêiner, relativo a /app |
PORT | 7860 | Porta em que o servidor escuta dentro do contêiner |
GUNICORN_WORKERS | 1 | Processos worker; qualquer outro valor interrompe o contêiner na inicialização |
GUNICORN_THREADS | 8 | Threads no único worker, e o número de requisições atendidas ao mesmo tempo |
GUNICORN_TIMEOUT | 120 | Segundos que um worker pode ficar em silêncio antes de o gunicorn reiniciá-lo |
POTATO_SECRET_KEY | aleatória a cada inicialização | Assina os cookies de sessão; secret_key na configuração tem precedência |
POTATO_ADMIN_API_KEY | arquivo gerado | A chave de API de administrador; admin_api_key na configuração tem precedência |
POTATO_GENERATED_TEMPLATES_DIR | escolhido na inicialização | Onde o Potato grava os templates de tarefa que ele monta na inicialização |
POTATO_BUNDLE_URL | não definida | URL de um .tar.gz do projeto a baixar em /app na inicialização |
POTATO_BUNDLE_SHA256 | não definida | Checksum com o qual o projeto baixado precisa coincidir |
POTATO_BUNDLE_TOKEN | não definida | Enviado como Authorization: Bearer junto com o download |
POTATO_RUN_AS_ROOT | não definida | 1 mantém o servidor rodando como root quando o contêiner inicia como root |
POTATO_ALLOW_MULTIWORKER | não definida | 1 permite que GUNICORN_WORKERS ultrapasse 1, o que perde anotações em um estudo real |
POTATO_ALLOW_READONLY_APP | não definida | 1 pula a verificação de inicialização de que /app é gravável |
Um contêiner que muda PORT precisa da mesma porta à direita de -p, como em -p 8000:9000 para PORT=9000.
Número de workers e de threads
O contêiner se recusa a iniciar com mais de um worker do gunicorn. O Potato mantém o pool de itens, a fila de atribuição e o estado de cada anotador na memória de um único processo. Um segundo worker teria uma cópia própria dos três e atribuiria itens que o primeiro worker já havia atribuído. Cada salvamento reescreve o user_state.json por inteiro, e o worker que salvasse por último descartaria as anotações do outro sem nenhum erro. Aumente GUNICORN_THREADS para ter mais requisições simultâneas, porque as threads compartilham uma única cópia do estado.
Templates gerados
O Potato monta um template para a página de anotação de cada tarefa na inicialização. Fora de um contêiner, o template vai para o pacote instalado sempre que o diretório do pacote é gravável. A imagem instala o Potato como root e roda o servidor como uid 1000, e por isso o servidor não consegue gravar no diretório do pacote. O Potato grava os templates, em vez disso, em um diretório sob /tmp que somente o usuário do servidor pode ler ou gravar, e o log de inicialização informa o diretório. O Potato reconstrói os templates a partir da configuração a cada inicialização, e perder o diretório não custa nada. Defina POTATO_GENERATED_TEMPLATES_DIR para colocar os templates em outro lugar, como uma montagem tmpfs.
Baixando o projeto na inicialização
Hosts que apenas executam uma imagem, como Render, Fly, Railway e ECS, não têm pasta para montar. Com POTATO_BUNDLE_URL definida, o contêiner baixa um .tar.gz do projeto, confere o download com POTATO_BUNDLE_SHA256 e descompacta o projeto em /app antes de o servidor iniciar. O contêiner grava o checksum em /app/.potato-bundle-sha, e uma reinicialização em um disco persistente pula um download cujo checksum coincide. Em um bundle construído por potato deploy, uma nova versão do projeto nunca substitui a saída das anotações nem os bancos SQLite que já estão no disco. potato deploy up define essas variáveis para você.
A chave de administrador em um contêiner
O painel de administração em /admin e a API de administração usam uma única chave compartilhada, enviada no cabeçalho X-API-Key ou digitada na página de login de administração. O Potato lê a chave primeiro da chave de configuração de nível superior admin_api_key e depois da variável de ambiente POTATO_ADMIN_API_KEY. Em um contêiner, a variável de ambiente mantém a chave fora do config.yaml e fora de qualquer repositório que guarde o projeto. O arquivo potato.env da seção sobre estudos longos define a variável, e a chave de administrador é então o que esse arquivo contém:
grep POTATO_ADMIN_API_KEY potato.envQuando nem a configuração nem o ambiente definem uma chave, o Potato gera uma chave na primeira vez que alguém abre /admin ou chama um endpoint de administração. O Potato grava a chave gerada em admin_api_key.txt no diretório da tarefa. No contêiner, o diretório da tarefa é /app, a pasta do projeto montada. O arquivo é legível somente pelo usuário do servidor, e o Potato lê o mesmo arquivo depois de uma reinicialização. O log do servidor informa o arquivo e nunca imprime a chave. Leia a chave por meio do contêiner:
docker exec potato cat /app/admin_api_key.txtEm um host Linux em que você entregou a pasta do projeto ao uid 1000, a sua própria conta não consegue ler admin_api_key.txt sem sudo. Um contêiner iniciado com --user "$(id -u):$(id -g)" grava o arquivo com a sua própria conta. Veja a página Painel do Administrador para saber o que a chave abre.
Modo de depuração em um contêiner
Mantenha debug: false em qualquer contêiner que sirva um estudo. Com debug: true na configuração, o Potato muda o comportamento de dois recursos:
- Páginas de administração sem chave. O painel de administração e a API de administração dispensam a chave para uma requisição vinda de um endereço de loopback que não traz os cabeçalhos
X-Forwarded-For,X-Real-IPnemForwarded. Na rede bridge padrão do Docker, uma requisição à porta publicada chega ao contêiner a partir do endereço da bridge e ainda precisa da chave. Com--network host, uma requisição da máquina host é uma requisição de loopback e não precisa de chave. Um proxy reverso no mesmo namespace de rede que não adiciona nenhum cabeçalho de encaminhamento faz com que toda requisição repassada pelo proxy pareça local. - Conta de depuração compartilhada. Com
debug_phasetambém definida, todo visitante entra como uma única conta compartilhada,debug_user, e é levado direto a essa fase. A conta não é de administrador, mas todos os visitantes compartilham as respostas dessa conta.
O servidor registra um aviso na inicialização sempre que debug está ligado. Um comando executado dentro do contêiner com docker exec é uma requisição de loopback. No modo de depuração, docker exec potato curl -s http://127.0.0.1:7860/admin/health retorna, portanto, o relatório de saúde de administração sem chave.
Verificações de integridade
GET /health não exige autenticação e informa apenas se o servidor está no ar. A rota retorna {"status": "ok"} quando o servidor está pronto e o status 503 enquanto o servidor ainda carrega os dados, e um balanceador de carga ou um orquestrador pode consultar a rota como verificação de prontidão. O HEALTHCHECK da própria imagem consulta /health a cada 30 segundos e permite um período de inicialização de 90 segundos para tarefas grandes, e docker ps mostra o resultado. GET /admin/health informa o nome da tarefa e o estado dos gerenciadores de itens e de anotadores, e a rota exige a chave de administrador no cabeçalho X-API-Key.
Docker Compose
O repositório do Potato não traz nenhum arquivo Compose. Configuração de Produção mostra um arquivo Compose que executa esta imagem atrás do nginx, com a pasta do projeto montada em /app e as chaves de sessão e de administrador lidas do ambiente.
Atualizando para uma nova versão
Atualizar o Potato substitui o contêiner e deixa no lugar a pasta do projeto, com todas as anotações e bancos de dados. Antes de trocar, baixe a nova versão, verifique a configuração com o validador da nova versão e faça um snapshot do banco de dados do projeto. Substitua 2.14.0 pela versão para a qual você está migrando:
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.sqliteEm seguida, substitua o contêiner em execução por um contêiner na nova tag, usando a mesma montagem e o mesmo arquivo de ambiente:
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 o potato.env mantém a mesma POTATO_SECRET_KEY, os anotadores continuam conectados durante a troca, e a chave de administrador não muda.
Solução de problemas
ERROR: config file not found: config.yaml. O projeto não foi montado onde o servidor procurou. O erro lista o conteúdo de /app para você comparar com o seu argumento -v. Uma listagem vazia geralmente indica um nome simples como -v myproject:/app. O Docker lê um nome simples como um novo volume vazio, e não como a sua pasta.
ERROR: /app is not writable by uid 1000. O servidor não consegue gravar na pasta do projeto. Entregue a pasta ao uid 1000 ou execute o contêiner com --user. Propriedade de arquivos no Linux traz os dois comandos.
output_annotation_dir ... is outside the project directory '/app'. A configuração aponta a saída das anotações para fora da pasta montada. Use um caminho relativo como annotation_output/.
Todos são desconectados depois de uma reinicialização. Nenhuma chave de assinatura foi definida, e o servidor escolheu uma nova chave aleatória na inicialização. Defina POTATO_SECRET_KEY em um arquivo de ambiente e passe o arquivo a todo docker run.
O contêiner termina com um erro que cita GUNICORN_WORKERS. O número de workers é maior que 1. Defina GUNICORN_WORKERS=1, ou remova a variável, e aumente GUNICORN_THREADS.
A página de login de administração rejeita todas as chaves. O admin_api_key da configuração tem precedência sobre POTATO_ADMIN_API_KEY, e os dois têm precedência sobre admin_api_key.txt. Verifique primeiro a configuração e depois docker exec potato printenv POTATO_ADMIN_API_KEY.
Leitura adicional
- Executar o Potato localmente ou na nuvem - destinos de
potato deployque executam esta imagem para você - Configuração de Produção - gunicorn, nginx, TLS, sessões e backups no seu próprio servidor
- Proxy reverso - servir o Potato sob um prefixo de caminho de URL
- Painel do Administrador - o que a chave de administrador abre
Para detalhes de implementação, veja a documentação-fonte.