Pular para o conteúdo

Contrato de App Nebular (para agents)

Este é o documento para colar ou mandar a um agent de IA que vai preparar um app para a Nebular. Ele é curto e normativo: cada “DEVE” é uma regra que a plataforma faz valer. O detalhe e os porquês estão em App pronto para a Nebular e para escalar; aqui só o contrato.

  1. Escutar em 0.0.0.0:$PORT. Ler PORT do ambiente; se ausente, usar 8080. Nunca 127.0.0.1, localhost nem porta fixa diferente da port declarada no serviço. Portas abaixo de 1024 não existem.
  2. Tratar o disco como somente leitura. A raiz e /app não aceitam escrita. Escrever só em volume montado ou em /tmp/$TMPDIR (64 MB, memória, apagado a cada deploy). O caminho de dados vem de variável de ambiente (ex.: DB_FILE, DATA_DIR) apontando para o volume, nunca fixo no código. Assets e arquivos gerados são criados no build, não no boot.
  3. Configurar tudo por variável de ambiente, com default seguro. Se faltar algo crítico (chave, URL, caminho), falhar no boot com mensagem clara e sair com código diferente de 0. Não cair em silêncio num default de desenvolvimento. Segredo nunca no repositório nem em NEXT_PUBLIC_*/VITE_*.
  4. Ligar o banco por referência. O banco não é injetado automaticamente: o serviço do app define uma variável como DATABASE_URL = ${{db.DATABASE_URL}} (db é o nome do serviço-banco), resolvida no deploy. Nunca montar hostname ou URL à mão. Veja Conexão e referências.
  5. Usar as variáveis automáticas que a plataforma injeta em todo serviço, sem você definir: PORT, PRIVATE_URL, PRIVATE_HOST, PUBLIC_URL, PUBLIC_DOMAIN, NEBULAR_SERVICE_NAME. Para falar com outro serviço use a referência ${{api.PRIVATE_URL}}. Lista e regras em Variáveis e referências. Não invente outras.
  6. Higiene de processo (detalhe em App pronto para escalar):
    • Escute primeiro, faça o trabalho pesado depois: bindar 0.0.0.0:$PORT e responder o health check (GET rápido, 2xx/3xx, sem migration nem chamada a terceiros) antes de qualquer tarefa longa. Importação, seed e migração de dados rodam em segundo plano (async), depois que a porta já está no ar — nunca no caminho do boot que trava a abertura da porta. Esse é o jeito robusto: a janela de ativação não estoura e a nova versão entra sem falhar.
    • SIGTERM gracioso: parar de aceitar conexões, terminar as em andamento e sair com 0.
    • Logs em stdout, uma linha JSON por evento; em falha de boot, logar a causa antes de sair.
    • Migrations idempotentes, com lock e compatíveis com a versão anterior.
    • Sem estado local (12-factor): sessão, fila, lock e upload não ficam na memória nem no disco do container; sem cron interno.
  7. Go com vários programas em cmd/. A plataforma escolhe por convenção (server, app, api, web, gateway, http, service, main, cmd, nessa prioridade). Se não decidir ou se não for o que você quer, defina build_command no serviço. O binário DEVE sair em /app/app. Detalhe em Go.

Boot lento: aumentar a janela de health check (healthcheck_start_period)

Seção intitulada “Boot lento: aumentar a janela de health check (healthcheck_start_period)”

A ativação de uma nova versão espera o app responder 2xx/3xx no health check por uma janela de 60s antes de trocar o tráfego. O padrão da casa é a regra 6 acima — escute primeiro, importe/semeie depois, em segundo plano. Quando o app precisa mesmo fazer trabalho longo no boot antes de escutar (e não dá para rodar async), aumente essa janela no serviço com o campo healthcheck_start_period (em segundos) em POST/PATCH .../services: 0 (padrão) mantém os 60s; um valor maior dá mais tempo ao primeiro 2xx/3xx, até o teto de 900s. Trade-off: uma janela maior atrasa a detecção de uma versão que subiu quebrada, então use só quando o boot longo for inevitável — escutar primeiro continua sendo o jeito certo.

Um deploy que falha termina em failed com um error.code estável (Deploys e status); a versão anterior continua no ar. Leia o log de build e do container (Logs) antes de mexer no código. Para o remédio passo a passo de cada código, com âncora por error_code, veja o cookbook Como corrigir cada erro de deploy — o fix_url de cada erro aponta para a seção correspondente lá.

Sintoma ou error_code Culpa Correção
activate_no_http App Escutar em 0.0.0.0:$PORT, declarar port igual, abrir a porta antes do trabalho pesado e corrigir o healthcheck_path se devolve 4xx/5xx. App que precisa importar/semear no boot: suba a janela do serviço com healthcheck_start_period (veja abaixo).
activate_readonly_write App Escrever só em volume ou /tmp; ou gerar o arquivo no build. Banco SQLite em disco somente-leitura? Migrar para libSQL gerenciado.
activate_app_crashed App Ver o log do container. Quase sempre é variável faltando, arquivo ausente ou escrita em disco somente leitura. Se o log mostra gravação de arquivo de banco em disco somente-leitura, migre o SQLite para libSQL gerenciado.
activate_oom App Reduzir memória (caches, concorrência) ou subir resources.memory_mb / de plano.
build_clone_failed App ou configuração Conferir repositório, branch e a instalação GitHub do serviço.
build_failed, build_install_failed, build_script_failed, build_detect_failed, build_copy_failed App ou configuração Ler o log de build; fixar versões; declarar build.builder/build_command; root_dir correto em monorepo.
build_oom, build_timeout, build_disk_full App Reduzir paralelismo e o que entra no artefato; tirar etapas pesadas do build.
image_requires_root App Declarar build.user (uid[:gid], nunca root) ou usar imagem sem privilégio.
unresolved_reference (422 ao disparar o deploy) App ou configuração Criar a variável ou serviço citado, ou corrigir o nome na referência.
Detecção de stack: vários mains em cmd/ Plataforma escolhe por convenção; se não decidir, é configuração Definir build_command, por exemplo go build -o /app/app ./cmd/server.

Falha com outro código, ou que persiste com o app correto: não é culpa do app. Reporte o error.code, o id do deploy e a hora (Fluxo de diagnóstico, Tabela de erros).

Quando a falha é por falta de persistência — activate_readonly_write (o app grava num caminho somente-leitura), conexão recusada a um banco inexistente ou unresolved_reference apontando a um banco que não existe — a plataforma também registra uma sugestão: ela detecta o que faltou (volume ou banco gerenciado) e oferece criar, esperando sua autorização. Veja Sugestões de persistência para ler e tratar essas sugestões pela API.