Banco de dados e migrações
A maior parte do que esta stack persiste não está em banco de dados nenhum, e a maioria dos operadores nunca roda uma migração. Este capítulo diz quais serviços têm um banco, o que cada um guarda, e qual é o único passo de schema que existe, feito uma única vez — para que você descubra rápido se ele se aplica a você.
Qual serviço guarda o quê
O Mycelium, o gateway, é dono da identidade. Contas, tenants, subscriptions, papéis de convidado e os tokens de magic link por trás do login vivem todos aqui. Onde depende do modo de deploy. No standalone é SQLite: deploy/standalone/config.standalone.toml define [sqlite] path = "/data/mycelium.db", e docker-compose.yaml monta o volume nomeado mycelium-data em /data. No prod é Postgres: o overlay acrescenta um serviço mycelium-postgres sobre postgres:16-alpine, apoiado no volume mycelium-postgres-data, e aponta o gateway para ele por MYC_BASE_DATABASE_URL.
O chat-webapp é dono da lista de conversas. O chat-webapp-postgres, também postgres:16-alpine, no volume chat-webapp-postgres-data, guarda um conjunto pequeno de tabelas: conversations (id, e-mail do dono, agente, título, ids de workspace, arquivo de sessão, projeto), conversation_tags, e uma tabela branding de uma linha só para o nome do app e os logos. É deliberadamente um banco separado do banco do Mycelium, com um ciclo de vida diferente; o arquivo compose defende esse ponto na definição do serviço.
O crab-shell-proxy é dono de um pequeno armazenamento chave-valor. No boot ele abre model-registry.db sob a raiz de dados do seu container (crab/crab-shell-proxy/cmd/crab-shell-proxy/main.go). Isso é um arquivo bbolt, não SQL: o Open de internal/registry/registry.go cria cada bucket de que precisa se estiver faltando e carrega o seu próprio marcador schema_version com uma migração no boot. Ele não precisa de nada de você.
Tudo o que um agente produz são arquivos, não linhas de tabela. Transcrições, arquivos de memória, projetos, skills, anexos entregues e os .schedules.json/.projects.json que pertencem ao proxy vivem todos em disco sob a raiz de dados, na árvore de tenants descrita em Agentes, workspaces e projetos. É por isso que um reset é um rm -rf e não um DROP.
O
deploy/prod/config.base.tomltambém traz um bloco[redis], comhostname = "mycelium-redis"epassword = "unused-no-redis-container-in-this-stack". O formato de configuração exige que as chaves existam; nenhum container Redis roda nesta stack. Não saia procurando por ele.
Quando você precisa fazer alguma coisa
Três desses quatro não precisam de nenhuma ação.
O adaptador SQLite do Mycelium carrega migrações embutidas — o deploy/prod/config.base.toml diz isso onde explica que o de Postgres não carrega — então o standalone funciona a partir de um checkout limpo, sem nenhum passo de schema.
O chat-webapp cria o seu próprio schema de forma preguiçosa, em tempo de execução, na primeira consulta. O crab/crab-exoskeleton-webapp/lib/db.ts tem uma função ensureSchema() que emite comandos CREATE TABLE IF NOT EXISTS e ALTER TABLE ... ADD COLUMN IF NOT EXISTS uma vez por processo e memoiza a promise. Todo comando é idempotente, e colunas novas são adicionadas de forma aditiva, então as linhas que já existiam sobrevivem. Não há ferramenta de migração, nem diretório de migrações, nem comando para rodar.
O registro de modelos se inicializa sozinho, como visto acima.
Sobra exatamente um caso: o backend Postgres do Mycelium, em prod, uma vez, depois do primeiro up.
O passo de schema único, só em prod
O adaptador Postgres do Mycelium não tem migrações embutidas, diferente do de SQLite. O deploy/prod/config.base.toml registra isso logo acima do seu bloco [diesel], e o docker-compose.prod.yaml repete no cabeçalho do arquivo. Enquanto o schema não for aplicado, o gateway tem um banco que não consegue usar, então ninguém consegue entrar.
São dois passos, nesta ordem: o up.sql do upstream, e depois os scripts de migração que o up.sql não incorpora. Os dois vêm do repositório do mycelium na mesma release que este deploy fixa.
git clone --depth 1 --branch 9.0.0-rc.13 \
https://github.com/LepistaBioinformatics/mycelium.git /tmp/myc
cd /path/to/zombie-crab-project && set -a; . ./.env; set +a
Carregar o .env é o que coloca MYC_DB_USER, MYC_DB_NAME e MYC_DB_PASSWORD no seu shell; esses são os mesmos três nomes que o docker-compose.prod.yaml entrega ao serviço mycelium-postgres, então eles vão bater com o que você definiu em deploy/prod/.env.example.
Passo 1 — o schema base.
docker compose -f docker-compose.yaml -f docker-compose.prod.yaml exec -T mycelium-postgres \
psql -U "$MYC_DB_USER" -d postgres \
-v db_name="$MYC_DB_NAME" -v db_user="$MYC_DB_USER" \
-v db_password="$MYC_DB_PASSWORD" -v db_role=service-role-mycelium \
< /tmp/myc/adapters/diesel_postgres/sql/up.sql
A conexão mira o banco de manutenção postgres porque um banco não pode ser criado de dentro de si mesmo. O README.md da raiz descreve o que o script faz em seguida — criar o banco da aplicação se ele estiver faltando, entrar nele, e criar os papéis e as tabelas — e registra que ele exige -v db_password. As flags -v são variáveis do psql que o script substitui dentro do seu próprio SQL.
O Compose já criou o banco e o papel de login quando você roda isso, porque o serviço
mycelium-postgresdeclaraPOSTGRES_DBePOSTGRES_USER. EntãoCREATE USER ... already existsé saída esperada aqui, não uma falha: opsqlimprime a mensagem e segue em frente.
Passo 2 — as migrações, em ordem de nome de arquivo.
for m in /tmp/myc/adapters/diesel_postgres/sql/migrations/*.sql; do
docker compose -f docker-compose.yaml -f docker-compose.prod.yaml exec -T mycelium-postgres \
psql -U "$MYC_DB_USER" -d "$MYC_DB_NAME" < "$m"
done
O glob expande em ordem lexical, que é a ordem que esses scripts esperam. Note que o -d aqui é o banco da aplicação, não postgres.
Este passo não é opcional na 9.0.0-rc.13. As notas do próprio repositório — o deploy/prod/config.base.toml acima de [diesel], e o README.md da raiz — registram que o up.sql nessa tag traz kv_artifact e o índice de claim de message_queue, mas não traz instance_settings, resource_audit_log, nem as colunas tenant.encrypted_dek e kek_version de que a criptografia por envelope precisa. Essas só existem como scripts de migração.
Para conferir o resultado, conecte e olhe:
docker compose -f docker-compose.yaml -f docker-compose.prod.yaml exec mycelium-postgres \
psql -U "$MYC_DB_USER" -d "$MYC_DB_NAME"
\dt deve listar instance_settings e resource_audit_log; \d tenant deve mostrar encrypted_dek e kek_version.
Os dois comandos acima são os que o
README.mdda raiz dá, e o nome do serviço, os nomes de variávelMYC_DB_*e a cadeia de arquivos compose neles batem todos com este checkout. O que este repositório não consegue verificar por conta própria é tudo o que está dentro do clone do mycelium: os caminhosadapters/diesel_postgres/sql/up.sqleadapters/diesel_postgres/sql/migrations/, o valordb_role=service-role-mycelium, o que oup.sqlfaz quando roda, e quais tabelas oup.sqldaquela tag cria e não cria. Tudo isso vem do repositório upstream. Se um caminho mudou de lugar em uma release posterior, leia o clone em vez desta página.
Em um deploy com Postgres que não é conduzido pelo arquivo compose deste repositório, rode os mesmos dois passos com docker exec direto contra o container mycelium-postgres. Nada no SQL muda; só como você chega no psql.
O que um reset mexe e o que ele não mexe
O reset descrito em Deploy remove diretórios sob data/. Ele não toca nos volumes nomeados, então tanto as contas e os papéis do Mycelium quanto a lista de conversas do chat-webapp sobrevivem a ele, e o seu login continua funcionando. Isso é deliberado: apagar o estado dos agentes é uma ação rotineira de desenvolvimento, e perder a sua conta Staff toda vez tornaria isso bem menos rotineiro.
Acrescentar -v ao docker compose down remove os volumes também. No standalone isso apaga o mycelium.db, então você teria de refazer o bootstrap da conta Staff do zero. No prod isso apaga o diretório de dados do Postgres, então você teria de refazer também os dois passos de schema acima.
Para onde ir agora
Deploy cobre os modos a que estes bancos pertencem, e Solução de problemas cobre como um schema faltando ou um volume apagado realmente aparecem no cliente de chat.