# 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](/operacao/app-pronto-para-escalar/); aqui só o contrato.

## Regras (o app DEVE)

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](/conectados/volumes/), 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](/bancos/conexao-e-referencias/).
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](/conectados/variaveis-e-referencias/). Não invente outras.
6. **Higiene de processo** (detalhe em [App pronto para escalar](/operacao/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](/guia/go/#vários-programas-em-cmd).

### 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

Um deploy que falha termina em `failed` com um `error.code` estável ([Deploys e status](/operacao/deploys-e-status/)); a versão anterior continua no ar. Leia o log de build e do container ([Logs](/operacao/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](/agents/corrigir-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](/agents/corrigir-deploy/#migrar-sqlite-local-para-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](/agents/corrigir-deploy/#migrar-sqlite-local-para-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](/erros/fluxo-de-diagnostico/), [Tabela de erros](/erros/tabela/)).

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](/conectados/sugestoes-de-persistencia/) para ler e tratar essas sugestões pela API.
