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.
Regras (o app DEVE)
Seção intitulada “Regras (o app DEVE)”- Escutar em
0.0.0.0:$PORT. LerPORTdo ambiente; se ausente, usar8080. Nunca127.0.0.1,localhostnem porta fixa diferente daportdeclarada no serviço. Portas abaixo de 1024 não existem. - Tratar o disco como somente leitura. A raiz e
/appnã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. - 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_*. - 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. - 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. - Higiene de processo (detalhe em App pronto para escalar):
- Escute primeiro, faça o trabalho pesado depois: bindar
0.0.0.0:$PORTe responder o health check (GETrá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. SIGTERMgracioso: 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.
- Escute primeiro, faça o trabalho pesado depois: bindar
- 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, definabuild_commandno 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.
Classificação de erros de deploy
Seção intitulada “Classificação de erros de deploy”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.