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 | |
|---|---|---|
| Comando | docker compose up -d | docker compose -f docker-compose.yaml -f docker-compose.prod.yaml up -d |
| Mycelium | construído a partir do código em MYCELIUM_GIT_REF | imagem publicada em MYCELIUM_IMAGE_TAG |
| Armazenamento do Mycelium | SQLite no volume mycelium-data | um serviço mycelium-postgres dedicado |
| transporte stub: os magic links são escritos no log | SMTP real | |
| Configuração do gateway | deploy/standalone/config.standalone.toml | deploy/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.exampleconstrói o commit9298ecb44a69ced91cb2d7d3356a716aedca858b, descrito ali como o commit com a tag9.0.0-rc.13, e odeploy/prod/.env.examplebaixaMYCELIUM_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-imagerodago vetego testantes de linkar o binário. Um teste que falha, portanto, impede a stack de subir. Odocker-compose.yamldiz 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.
!resetexige 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-proxyrecebeports: !reset []. O arquivo base publica o proxy em127.0.0.1:18080para testes diretos, eportsconcatena entre as camadas de-fem 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-webappainda é 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 buildVITE_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.ymldocrab/crab-ganglion-harnesspublicaghcr.io/lepistabioinformatics/crab-ganglion:sha-<short-sha>a cada push namain, deliberadamente sem:lateste 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 é orelease-picoclaw-glob.yml, que publica a imagem picoclaw com patch.Até o perfil se atualizar, defina
CRAB_GANGLION_IMAGEno seu.envde produção com uma tagsha-específica ou um digest, e rodedocker pullnela antes de subir a stack. Ointernal/config/config.godocrab/crab-shell-proxypede 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 oEnsureImagenunca baixa o que já está presente.O pull não é faxina opcional. O serviço
ganglion-imageherdado pega oimage:do que você definir, mantendo o seubuild:context, então se essa referência não existir localmente oupvai 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_ROOTprecisa 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
noreplyEmailesupportEmailemdeploy/prod/config.base.tomlcom o mesmo endereço deMYC_SMTP_USERNAME. O Gmail rejeita umFromque 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
domainUrleallowedOriginsno mesmo arquivo junto com o argumento de buildVITE_MYCELIUM_API_URLdomycelium-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.