Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Deploy

Este capítulo é para quem roda a stack numa máquina. Ele descreve os dois modos de deploy que existem, para que serve cada um e o comando exato que sobe cada um deles. Ele também diz com todas as letras onde a produção ainda não está pronta, para que você descubra isso aqui e não por um container que falhou.

O que é um modo

A stack é um conjunto de serviços do Docker Compose. Um modo é uma combinação de arquivos compose e um arquivo .env na raiz do repositório. Há um arquivo base que sempre participa, docker-compose.yaml, e overlays que mudam alguns dos serviços dele sem substituí-los.

Dois modos vêm como perfis, cada um com seu próprio diretório em deploy/ guardando o .env.example daquele modo e a configuração de gateway que ele monta:

standalone (o padrão)prod
Comandodocker compose up -ddocker compose -f docker-compose.yaml -f docker-compose.prod.yaml up -d
Myceliumconstruído a partir do código em MYCELIUM_GIT_REFimagem publicada em MYCELIUM_IMAGE_TAG
Armazenamento do MyceliumSQLite no volume mycelium-dataum serviço mycelium-postgres dedicado
E-mailtransporte stub: os magic links são escritos no logSMTP real
Configuração do gatewaydeploy/standalone/config.standalone.tomldeploy/prod/config.base.toml

Um terceiro arquivo, docker-compose.observability.yaml, não é um modo. É um overlay opcional que dá ao watcher um lugar para onde mandar suas métricas; ele é descrito em Observabilidade.

Os dois perfis fixam a mesma versão do Mycelium. O deploy/standalone/.env.example constrói o commit 9298ecb44a69ced91cb2d7d3356a716aedca858b, descrito ali como o commit com a tag 9.0.0-rc.13, e o deploy/prod/.env.example baixa MYCELIUM_IMAGE_TAG=9.0.0-rc.13. Mova os dois juntos. A configuração do gateway é vocabulário compartilhado entre os dois, e uma diferença de versão entre o que você testa e o que vai para o deploy é onde isso quebra.

Desenvolvimento num laptop: standalone

Copie o arquivo de ambiente do perfil para a raiz do repositório e edite-o:

cp deploy/standalone/.env.example .env

Os valores que você precisa definir antes do primeiro up são os bearer tokens de cada agente (MYC_PICOCLAW_ALPHA_TOKEN, MYC_PICOCLAW_BETA_TOKEN), a chave de LLM própria de cada agente (PICOCLAW_ALPHA_API_KEY, PICOCLAW_BETA_API_KEY) e MYC_STANDALONE_BOOTSTRAP_SECRET, que controla o registro único da conta Staff. O bearer token é o que o Mycelium injeta em uma requisição para aquele agente e o que o crab-shell-proxy valida; a chave de LLM é lida do ambiente do proxy e nunca é escrita em uma imagem ou em um arquivo versionado.

Depois:

docker compose up -d --build

O standalone constrói em vez de baixar. Tudo sob crab/ — o proxy, o chat webapp, o ganglion harness, o watcher — é construído a partir da sua árvore de trabalho, que é justamente o objetivo do modo: o que roda é o que você tem no checkout. O Mycelium é a exceção. fungi/mycelium/Dockerfile.standalone constrói o mycelium-api a partir do repositório git upstream na referência fixada em MYCELIUM_GIT_REF, sem fonte local, e fungi/mycelium-webapp faz o mesmo com a UI de administração. É também por isso que essa referência e o MYCELIUM_IMAGE_TAG do prod precisam avançar juntos.

deploy/standalone/.env.example deliberadamente não traz tags de imagem, porque CRAB_SHELL_PROXY_TAG e CHAT_WEBAPP_TAG não fazem nada em um modo que constrói.

Os dois serviços que só constroem

Dois serviços em docker-compose.yaml não são servidores. picoclaw-image e ganglion-image constroem uma imagem cada, rodam /bin/true e saem com zero; o crab-shell-proxy declara condition: service_completed_successfully para os dois, então as imagens existem com certeza antes que qualquer coisa possa criar um container a partir delas.

Eles existem porque os containers dos agentes são criados pelo crab-shell-proxy pelo socket do Docker, e não pelo Compose, então o Compose nunca os construiria nem os baixaria. A ausência deles é também o assunto de uma das falhas em Solução de problemas.

O build do ganglion-image roda go vet e go test antes de linkar o binário. Um teste que falha, portanto, impede a stack de subir. O docker-compose.yaml diz que essa é a intenção para um arquivo compose de desenvolvimento: os testes são o critério de aceitação da imagem.

Zerando tudo

Para apagar todos os agentes por usuário e todos os templates e deixar a stack se reconstruir sozinha:

docker compose down
docker rm -f $(docker ps -aq --filter 'name=crabshell') 2>/dev/null
sudo rm -rf data/templates data/tenants data/effective-secrets \
            data/effective-skills data/user-secrets data/registered-models
docker compose up -d --build

A linha docker rm é necessária porque os containers por usuário foram criados pelo proxy, não pelo Compose, então o docker compose down não sabe deles. O sudo é necessário porque a árvore em disco sob data/ é escrita pelo proxy como root. O --build não é opcional: o template de fallback com o qual o proxy repovoa um data/ apagado está embutido no binário do proxy.

Contas e papéis não ficam em data/. Eles vivem em volumes nomeados — mycelium-data para o banco SQLite do próprio Mycelium e chat-webapp-postgres-data para a lista de conversas — então o seu login sobrevive à limpeza. Acrescentar -v ao docker compose down reseta esses também, e aí você teria de refazer o bootstrap da conta Staff. Veja Banco de dados e migrações.

Produção: o overlay prod

cp deploy/prod/.env.example .env
docker compose -f docker-compose.yaml -f docker-compose.prod.yaml up -d

O overlay muda quatro serviços. mycelium-gateway, crab-shell-proxy, chat-webapp e harness-sphere recebem cada um build: !reset null e um image: apontando para uma imagem publicada no GHCR. O !reset importa: um image: simples ao lado de um build: herdado ainda construiria a imagem localmente se ela estivesse faltando, e toda a postura deste modo é que esses quatro são baixados, nunca construídos.

!reset exige o Docker Compose v2.24 ou mais novo. Isso é um pré-requisito do modo prod, não um detalhe — um Compose mais antigo não vai entender o arquivo.

Outras duas coisas que o overlay faz passam fácil despercebidas e as duas são deliberadas:

  • O crab-shell-proxy recebe ports: !reset []. O arquivo base publica o proxy em 127.0.0.1:18080 para testes diretos, e ports concatena entre as camadas de -f em vez de substituir, então sem o reset aquela porta continuaria ativa. Em produção o gateway precisa ser o único ponto de entrada, loopback incluído.
  • O mycelium-webapp ainda é construído aqui. Ele é uma single-page app que roda no navegador e cuja URL de API é embutida no momento do build pelo argumento de build VITE_MYCELIUM_API_URL, então ele não pode ser uma imagem pré-construída genérica.

Produção hoje não tem imagem publicada do ganglion

Esta é a única coisa sobre a qual vale ser explícito, porque um leitor que faz deploy em um servidor e configura um agente ganglion pode esbarrar nela.

O docker-compose.prod.yaml não define CRAB_GANGLION_IMAGE, e o deploy/prod/.env.example também não o menciona. Como o prod é um overlay sobre o arquivo base, o que de fato chega ao proxy é o padrão do arquivo base, zombie-crab/crab-ganglion:dev — uma tag que só existe na máquina que a construiu. O agente, portanto, não está desligado; ele está apontado para um nome que nenhum registry resolve.

Duas consequências vêm daí. Primeiro, o comando de prod herda sem mudanças o serviço ganglion-image, que só constrói, já que o overlay reseta apenas os quatro serviços acima. Um up -d de prod em um host que carrega as fontes dos submódulos vai construir o ganglion localmente, o que não é o que “imagens publicadas” leva você a esperar e o que exige a árvore de fontes e um build de Go naquele host. Segundo, em qualquer situação em que esse serviço não rode — um docker compose up -d crab-shell-proxy sozinho, um docker system prune que removeu a tag local, ou um deploy que apenas baixa imagens — o EnsureImage do proxy não acha nada localmente, cai para um pull no registry, e esse pull dá 404. Todo agente ganglion fica morto até alguém construir a imagem na mão.

Imagens imutáveis do ganglion existem, sim. O próprio .github/workflows/release.yml do crab/crab-ganglion-harness publica ghcr.io/lepistabioinformatics/crab-ganglion:sha-<short-sha> a cada push na main, deliberadamente sem :latest e sem nenhuma tag que seja reconstruída. O que falta é a ligação: nada no perfil de prod aponta para uma delas. O único workflow de release no .github/workflows/ deste repositório é o release-picoclaw-glob.yml, que publica a imagem picoclaw com patch.

Até o perfil se atualizar, defina CRAB_GANGLION_IMAGE no seu .env de produção com uma tag sha- específica ou um digest, e rode docker pull nela antes de subir a stack. O internal/config/config.go do crab/crab-shell-proxy pede uma referência imutável e explica por quê: uma tag móvel já deixou um host rodando um binário de três semanas atrás, silenciosamente, porque o EnsureImage nunca baixa o que já está presente.

O pull não é faxina opcional. O serviço ganglion-image herdado pega o image: do que você definir, mantendo o seu build: context, então se essa referência não existir localmente o up vai construir a fonte do submódulo que está no checkout e marcar esses bytes com o nome publicado — que é exatamente a falha de bytes-errados-sob-um-nome-confiável que a referência imutável existe para evitar.

Note também que um agente que não declara nenhuma chave harness: é hoje um agente ganglion (DefaultHarness em crab/crab-shell-proxy/internal/config/config.go), então ele também precisa de CRAB_GANGLION_IMAGE. Declare o harness explicitamente em todo agente; a configuração que vem neste repositório faz isso, por esse motivo.

Antes de um deploy de produção

  • CRAB_HOST_DATA_ROOT precisa ser um caminho absoluto do host. O crab-shell-proxy entrega esse caminho ao daemon Docker do host como origem do bind-mount dos containers que ele cria, então um caminho que só existe dentro do proxy não vai resolver.
  • Defina noreplyEmail e supportEmail em deploy/prod/config.base.toml com o mesmo endereço de MYC_SMTP_USERNAME. O Gmail rejeita um From que não bate. A porta SMTP é fixa em 465 nesse arquivo em vez de ser uma variável de ambiente, porque o Mycelium a interpreta como número e um valor de ambiente é uma string.
  • Se você colocar um hostname na frente da stack, mude domainUrl e allowedOrigins no mesmo arquivo junto com o argumento de build VITE_MYCELIUM_API_URL do mycelium-webapp. A UI de administração chama o gateway direto do navegador, então uma divergência entre os dois é um muro de CORS.
  • O catálogo de agentes é embutido na imagem do proxy nos dois modos (crab/crab-shell-proxy/config.yaml), então adicionar ou remover um agente significa reconstruí-la. Montar o seu próprio arquivo sobre /etc/crab-shell-proxy/config.yaml é suportado, se você preferir.
  • Todo agente roteado precisa do seu bearer token definido. Deixar um vazio faz o gateway anunciar o agente e injetar um bearer vazio, o que falha na hora da requisição e não no boot.

Não existe perfil Dokploy

Existiu um terceiro perfil, docker-compose.dokploy.yaml com o seu próprio diretório deploy/dokploy/, e ele foi removido em vez de movido. O deploy para o qual ele foi escrito agora vive em um repositório separado, sobe apenas a metade CRAB da stack, e tinha virado a fonte da verdade de fato enquanto esta cópia era mantida na fé.

Se você está fazendo deploy no Dokploy, comece pelo prod, que é o perfil mais próximo, e acrescente os labels do Traefik e a rede externa de que a sua instalação precisa. git log -- docker-compose.dokploy.yaml deploy/dokploy/ ainda tem o original.

Para onde ir agora

Produção em Postgres precisa de um passo de schema, feito uma única vez, antes que alguém consiga entrar — é o Banco de dados e migrações. Com a stack no ar, Observabilidade cobre o watcher e o backend opcional de métricas, e Solução de problemas reúne as falhas com que as pessoas realmente esbarram.