Arquitetura

Entender o que roda onde deixa todas as outras decisões operacionais — escala, backup, política de rede, upgrades — muito mais simples.

Serviços

ServiçoImagemPortaSem estadoObrigatório
appghcr.io/seeyuai/studio3000Somente com object storage configuradoSim
realtimeghcr.io/seeyuai/realtime3002SimSim
migrationsghcr.io/seeyuai/migrations—Sim (roda uma vez)Sim
postgresqlpgvector/pgvector:pg175432NãoSim
redisredis:7-alpine6379Em grande parteIncluído nos dois; troque por uma instância gerenciada em produção
cronghcr.io/seeyuai/cron (Compose) / curlimages/curl (CronJobs)—SimSim
piighcr.io/seeyuai/pii5001SimOpcional
ollamaollama/ollama11434Não (cache de modelos)Opcional
telemetryotel/opentelemetry-collector-contrib4317/4318SimOpcional

app

A aplicação Next.js: a interface do editor, todas as rotas de API e o motor de execução de workflows. Por padrão, as execuções de workflow acontecem dentro do processo do app, usando um sandbox isolated-vm — é por isso que o recurso limitante é a memória, e não a CPU. Tanto o chart quanto o arquivo compose solicitam 4 Gi e limitam o app a 8 Gi. Configurar um provedor de sandbox remoto (E2B ou Daytona) tira a execução de código do processo; veja Segurança.

Qualquer réplica pode atender qualquer requisição desde que o object storage esteja configurado. Até então, o app grava os uploads no sistema de arquivos do próprio contêiner, o que o torna stateful — veja Onde o estado fica. Escale horizontalmente só depois de ler Escala e HA, por causa dos pré-requisitos de Redis, storage e pool de conexões.

realtime

Um servidor Socket.IO em Bun que cuida da edição colaborativa, das atualizações de execução ao vivo e dos documentos colaborativos. Os clientes se conectam em /socket.io.

Ele compartilha o banco de dados e o BETTER_AUTH_SECRET com o app (o padrão de sessão compartilhada em banco do Better Auth), então autentica os mesmos usuários sem um login separado.

Escalar o realtime para além de uma réplica exige REDIS_URL — é o adaptador Redis do Socket.IO que leva os eventos entre os pods. Sem ele, dois usuários em pods diferentes simplesmente param de ver as edições um do outro.

migrations

Aplica as migrações de schema do Drizzle e encerra. No Docker Compose é um serviço de execução única; no Kubernetes é um init container no Deployment do app, então as migrações rodam antes de qualquer pod do app ficar pronto e voltam a rodar (sem efeito) em cada rollout.

As migrações são somente para frente. Veja Upgrades.

postgresql

PostgreSQL 17 com a extensão pgvector, que é obrigatória — os embeddings da base de conhecimento são armazenados e pesquisados como vetores. A imagem pgvector/pgvector:pg17 já a inclui; uma instância gerenciada precisa ter a extensão habilitada (as migrações do Studio executam CREATE EXTENSION automaticamente quando as permissões permitem).

É aqui que fica praticamente todo o estado durável: workflows, execuções, logs, usuários, organizações, credenciais, chunks e embeddings da base de conhecimento, e os dados das tabelas.

redis

Dá suporte a pub/sub, ao adaptador do Socket.IO, ao armazenamento de idempotência, aos marcadores de progresso de execução, aos limites distribuídos de execução e ao armazenamento de aprovações da autenticação por CLI. Os usos parecidos com armazenamento recorrem ao Postgres ou a estado em processo. O pub/sub recorre a um emissor local ao processo, o que funciona bem com uma réplica e descarta todos os eventos entre pods quando há mais de uma. Veja Redis.

cron

Dezoito jobs agendados que chamam endpoints internos — execução de agendamentos, triggers de polling, renovação de assinaturas de webhook, syncs de conectores, processamento do outbox, drenagem de dados e limpeza de imagens de sandbox. O Kubernetes os executa como CronJobs; o Docker Compose os executa a partir de um único serviço supercronic. Mesmos caminhos, mesmos agendamentos. Veja Jobs em segundo plano.

Onde o estado fica

Três lugares, uma vez que a implantação esteja configurada para produção. Todo o resto é descartável.

ArmazenamentoConteúdoBackup
PostgreSQLTodos os dados da aplicaçãopg_dump / snapshots gerenciados + PITR
Object storageArquivos enviados, documentos da base de conhecimento, saídas de execução, avatares, logotiposVersionamento de bucket + ciclo de vida
SecretsENCRYPTION_KEY, API_ENCRYPTION_KEY, BETTER_AUTH_SECRET, INTERNAL_API_SECRET, CRON_SECRETGerenciador de secrets

O object storage não vem configurado por padrão, e o fallback não é durável. O Studio só usa S3, Azure Blob ou GCS quando as variáveis correspondentes estão definidas (S3_BUCKET_NAME + AWS_REGION, AZURE_STORAGE_CONTAINER_NAME + credenciais, ou GCS_BUCKET_NAME). Sem nenhuma delas, ele grava os uploads em um diretório dentro do contêiner do app — e nem o docker-compose.prod.yml nem o Helm chart montam um volume ali. Os arquivos são perdidos quando o contêiner é recriado e ficam invisíveis para as outras réplicas. Configure o object storage antes de armazenar qualquer coisa que importe, e antes de passar de uma réplica.

A ENCRYPTION_KEY não é recuperável nem derivável. Ela criptografa as variáveis de ambiente do workspace e pessoais, as chaves de API de provedores armazenadas, as credenciais OAuth de MCP e os secrets de deploy e de chat em repouso — restaurar o banco de dados com uma chave diferente resulta em uma aplicação funcional em que nada disso pode ser descriptografado. Faça o backup dela separadamente do banco de dados, e nunca a rotacione sem necessidade.

O Redis é um cache e um barramento de mensagens. Perdê-lo derruba as atualizações ao vivo em andamento; não perde dados já confirmados.

Caminhos das requisições

Editor / API — navegador → ingress/proxy reverso → app:3000 → Postgres, Redis, object storage.

Colaboração — navegador → ingress → realtime:3002 (/socket.io, upgrade para WebSocket) → pub/sub do Redis → outros pods de realtime. O proxy precisa repassar os cabeçalhos de upgrade e permitir conexões ociosas de longa duração; veja Rede.

Upload de arquivo (com object storage configurado) — o navegador pede ao app para abrir uma sessão de upload → o app devolve instruções de transferência assinadas → o navegador envia os bytes diretamente ao object storage (um PUT de até 50 MB, partes multipart acima disso) → o navegador avisa ao app que a sessão terminou e o app registra os metadados. É por isso que os buckets precisam de uma política de CORS nomeando a origem do seu Studio. Os downloads são feitos por proxy através do app.

Upload de arquivo (disco local) — a mesma sessão de upload é aberta, mas as instruções de transferência apontam de volta para os endpoints /api/v2/uploads/... do próprio app, em vez de um bucket, então os bytes passam pelo app. Não há configuração de CORS envolvida, e nenhum bucket é usado.

Execução de workflow — trigger (manual, API, webhook ou agendamento) → o app enfileira ou executa inline → sandbox isolated-vm → resultados e logs no Postgres, marcadores de progresso no Redis.

Trabalho em segundo plano — CronJob → Authorization: Bearer $CRON_SECRET → endpoint do app → mesmo caminho de execução.

Fronteiras de rede

DeParaFinalidade
Internetapp:3000, realtime:3002Usuários
app, realtimepostgresql:5432Dados
app, realtimeredis:6379Pub/sub, cache
appEndpoint do object storageArquivos (lado servidor)
NavegadorEndpoint do object storageUploads pré-assinados — precisa ser acessível publicamente
appAPIs de provedores de modelo, APIs de integrações, provedor de SMTP/e-mailSaída
cronapp:3000 (Service interno / rede do compose)Triggers agendados

A NetworkPolicy opcional do chart bloqueia por padrão os endpoints de metadados da nuvem (169.254.169.254), mas permite ingress de qualquer pod do cluster, a menos que você restrinja networkPolicy.ingressFrom. Veja Segurança.

Common Questions

Um servidor realtime precisa existir — é ele que carrega a edição colaborativa e as atualizações de execução ao vivo, e o editor fica bastante degradado sem um. Você pode definir realtime.enabled=false no chart, mas somente apontando app.env.SOCKET_SERVER_URL para uma instância de realtime que você mesmo mantém.
Por padrão, não — as execuções rodam dentro do processo do app usando um sandbox isolated-vm, e é por isso que o app tem um limite de 8 Gi de memória. Definir E2B_ENABLED ou SANDBOX_PROVIDER=daytona move o código do usuário para um sandbox remoto, e TRIGGER_DEV_ENABLED direciona os jobs assíncronos para o Trigger.dev; caso contrário, é usada a fila de jobs baseada em banco de dados.