Pular para o conteúdo

Site estático

Um site estático não tem container de execução: a borda serve os arquivos. Pode ter um passo de build (Vite, Astro, Hugo via package.json) ou ser HTML puro.

  • Token com os escopos projects:write, deploy, env:write e logs:read (veja Token de API e Tokens e escopos). Para listar projetos e serviços, projects:read.
  • NEBULAR_API e NEBULAR_TOKEN no ambiente; curl e jq instalados.
  • Toda chamada que cria algo leva Idempotency-Key: repetir após timeout é seguro.

Com builder: "auto" (ou "static") a Nebular decide assim (um Dockerfile, se existir, vence):

  • Sem package.json, com index.html na raiz: publica os arquivos como estão.
  • package.json sem scripts.start: instala (npm ci/npm install) e roda npm run build (ou o build_command).
  • A pasta publicada é a primeira, nesta ordem, que contenha index.html: dist, out, build, public, a raiz. Nenhum index.html encontrado é erro.
  • Não há processo para consultar variáveis em runtime: valores só entram em tempo de build. Por isso, nunca ponha segredo em variável de site estático: ela vai parar no bundle do browser.
  • port e healthcheck_path não se aplicam ao estático.

Regras comuns a todas as stacks:

  • O app precisa escutar em 0.0.0.0 na porta que o serviço declara em port e que a plataforma também entrega na variável PORT. Leia PORT do ambiente; declare port no serviço com o mesmo valor.
  • O container roda sem root, com sistema de arquivos somente leitura e /tmp em tmpfs (64 MB). Não grave em outro lugar; para dados persistentes use um volume ou um banco.
  • Segredo vai em variável do serviço (env:write), nunca no código, nunca em NEXT_PUBLIC_* ou equivalente de bundle de browser.
{
"scripts": { "build": "vite build" }
}

O projeto é o grupo: todos os seus serviços compartilham uma rede privada. Guarde o id da resposta.

Janela do terminal
NAME="site-$(date +%s)-$RANDOM"
curl -sS -X POST "$NEBULAR_API/v1/projects" \
-H "Authorization: Bearer $NEBULAR_TOKEN" -H "Content-Type: application/json" \
-H "Idempotency-Key: $NAME-projeto" \
-d "{\"name\": \"$NAME\"}"
{"id": "01j9zkk07fhzv5zsmn996cdwpb", "name": "site-1760000000"}
Resposta O que fazer
401 Token ausente ou inválido: confira o cabeçalho; peça outro token a uma pessoa.
403 Falta projects:write: peça um token com o escopo.
409 slug_taken / project_quota_exceeded Já existe projeto com esse slug (derivado do nome): escolha outro nome (ou repita com a mesma Idempotency-Key para recuperar a resposta anterior); ou o time atingiu o limite de projetos.
429 Espere Retry-After segundos e repita.

O serviço web é público (visibility: "public" exige um slug único) e usa fonte upload e o builder static. Este bloco é autossuficiente: cria o projeto e o serviço.

Janela do terminal
NAME="site-$(date +%s)-$RANDOM"
P=$(curl -sS -X POST "$NEBULAR_API/v1/projects" \
-H "Authorization: Bearer $NEBULAR_TOKEN" -H "Content-Type: application/json" \
-H "Idempotency-Key: $NAME-projeto" \
-d "{\"name\": \"$NAME\"}" | jq -r .id)
curl -sS -X PATCH "$NEBULAR_API/v1/projects/$P/services/web" \
-H "Authorization: Bearer $NEBULAR_TOKEN" -H "Content-Type: application/json" \
-d "{
\"visibility\": \"public\",
\"slug\": \"$NAME\",
\"source\": {\"type\": \"upload\"},
\"build\": {\"builder\": \"static\"},
\"port\": 3000
}"
{"id": "01j9zk32py7tcbz2q0ra5aqx7k", "name": "web", "kind": "app", "visibility": "public", "port": 3000}

Campos úteis do serviço: healthcheck_path (caminho HTTP que deve responder 2xx), start_command e build_command (substituem o padrão), resources ({"memory_mb": 512, "cpus": 0.5}, valores padrão; sempre dentro de resources) e auto_deploy.

Resposta O que fazer
400 invalid_request Corpo inválido: o name do serviço segue ^[a-z][a-z0-9-]{0,30}$; build.builder deve ser um de auto, node, go, rust, python, static, dockerfile.
404 Projeto inexistente ou de outro time (a API não diferencia). Confira o id.
409 name_taken / slug_taken Já existe serviço com esse nome no projeto (todo projeto nasce com um web), ou o slug está em uso.

Defina as variáveis antes do deploy: elas são resolvidas na hora do deploy. O valor pode referenciar outros serviços (${{db.DATABASE_URL}}) ou o grupo (${{shared.VAR}}); veja Variáveis e referências.

Janela do terminal
NAME="site-$(date +%s)-$RANDOM"
P=$(curl -sS -X POST "$NEBULAR_API/v1/projects" \
-H "Authorization: Bearer $NEBULAR_TOKEN" -H "Content-Type: application/json" \
-H "Idempotency-Key: $NAME-projeto" \
-d "{\"name\": \"$NAME\"}" | jq -r .id)
S=$(curl -sS -X PATCH "$NEBULAR_API/v1/projects/$P/services/web" \
-H "Authorization: Bearer $NEBULAR_TOKEN" -H "Content-Type: application/json" \
-d "{
\"visibility\": \"public\",
\"slug\": \"$NAME\",
\"source\": {\"type\": \"upload\"},
\"build\": {\"builder\": \"static\"},
\"port\": 3000
}" | jq -r .id)
curl -sS -X PUT "$NEBULAR_API/v1/projects/$P/services/$S/variables/VITE_API_URL" \
-H "Authorization: Bearer $NEBULAR_TOKEN" -H "Content-Type: application/json" \
-d '{"value": "https://api.exemplo.com"}'

Resposta: 200. Variáveis NEXT_PUBLIC_*, VITE_* e similares vão para o bundle do browser: nunca ponha segredo nelas.

Resposta O que fazer
403 Falta env:write.
400 invalid_request Nome de variável inválido ou valor acima de 64 KiB.
unresolved_reference / reference_cycle Aparecem no deploy, não aqui (o PUT aceita a referência): o POST do deploy responde 422 e nada é criado; details.references lista os nomes com problema (uma ${{...}} aponta para variável inexistente ou forma ciclo). Corrija a variável citada.

Compacte a pasta do projeto (sem node_modules, .git nem arquivos .env) e envie como multipart/form-data para o serviço. A fonte upload não usa git: cada envio é um deploy. Se o serviço tem fonte git ({"type": "git", "installation_id": "...", "repo": "org/repo", "branch": "main"}) ou image, o POST de deploy usa a fonte configurada no serviço.

Janela do terminal
# preparo (passos 1 e 2): projeto e serviço, para o bloco rodar sozinho
NAME="site-$(date +%s)-$RANDOM"
P=$(curl -sS -X POST "$NEBULAR_API/v1/projects" \
-H "Authorization: Bearer $NEBULAR_TOKEN" -H "Content-Type: application/json" \
-d "{\"name\": \"$NAME\"}" | jq -r .id)
S=$(curl -sS -X PATCH "$NEBULAR_API/v1/projects/$P/services/web" \
-H "Authorization: Bearer $NEBULAR_TOKEN" -H "Content-Type: application/json" \
-d "{
\"visibility\": \"public\",
\"slug\": \"$NAME\",
\"source\": {\"type\": \"upload\"},
\"build\": {\"builder\": \"static\"},
\"port\": 3000
}" | jq -r .id)
H="Authorization: Bearer $NEBULAR_TOKEN"
# código: a pasta do projeto, sem node_modules, .git nem .env
ARQ=$(mktemp)
tar --exclude=node_modules --exclude=.git --exclude='.env*' -czf "$ARQ" .
RESP=$(curl -sS -X POST "$NEBULAR_API/v1/projects/$P/services/$S/deployments" \
-H "$H" \
-H "Idempotency-Key: deploy-$(sha256sum "$ARQ" | cut -c1-16)-$RANDOM" \
-F "file=@$ARQ")
D=$(echo "$RESP" | jq -r .id) # resposta: 202 {"id": "dep_...", "status": "queued"}
# polling com backoff: 2 s, dobrando até 30 s, no máximo 40 tentativas (~10 min)
delay=2
for i in $(seq 1 40); do
st=$(curl -sS "$NEBULAR_API/v1/projects/$P/services/$S/deployments/$D" -H "$H" | jq -r .status)
echo "tentativa $i: $st"
case "$st" in
ready) break ;;
failed|blocked|canceled|superseded) echo "deploy terminou em $st" >&2; exit 1 ;;
esac
sleep "$delay"; delay=$(( delay * 2 > 30 ? 30 : delay * 2 ))
done
[ "$st" = ready ]
# a URL pública do serviço vem em public_url
curl -sS "$NEBULAR_API/v1/projects/$P/services/$S" -H "$H" | tee /dev/stderr | jq -e '.public_url | startswith("https://")' >/dev/null

Com o deploy ready, chame <public_url>/healthz (esperado 200; exige DNS e o container no ar, por isso esse último passo não roda no teste).

Estados do deploy: queued, building, deploying, ready (sucesso), e os finais de falha failed, blocked (um serviço do qual este depende falhou), canceled, superseded (um deploy mais novo o substituiu). Não há webhooks: faça polling. Detalhes em Deploys e status.

Estado ou resposta O que fazer
failed Leia o log de build (passo 5). Corrija o código ou a configuração e faça um novo deploy. Não repita igual.
422 unresolved_reference ao disparar o deploy Variável referenciada não existe: crie-a ou corrija o nome (details.references diz qual).
422 reference_cycle ao disparar o deploy Duas variáveis se referenciam: quebre o ciclo.
blocked Corrija primeiro o serviço do qual este depende.
404 O deploy ou serviço não existe ou não é seu.
401 / 403 Token inválido / sem escopo deploy. Pare e peça outro token.
429 Espere Retry-After e repita o GET (o polling já faz backoff).
503 Transitório: repita com backoff.
quota_exceeded Limite de serviços, memória ou volume do plano: veja Limites e cotas.
failed: “nenhum index.html encontrado” Gere o index.html em dist, out, build, public ou na raiz (confira o build_command).

Log do build (deploys failed) e log de execução, ambos com paginação por cursor; precisam do escopo logs:read.

Janela do terminal
# preparo (passos 1 e 2): projeto e serviço, para o bloco rodar sozinho
NAME="site-$(date +%s)-$RANDOM"
P=$(curl -sS -X POST "$NEBULAR_API/v1/projects" \
-H "Authorization: Bearer $NEBULAR_TOKEN" -H "Content-Type: application/json" \
-d "{\"name\": \"$NAME\"}" | jq -r .id)
S=$(curl -sS -X PATCH "$NEBULAR_API/v1/projects/$P/services/web" \
-H "Authorization: Bearer $NEBULAR_TOKEN" -H "Content-Type: application/json" \
-d "{
\"visibility\": \"public\",
\"slug\": \"$NAME\",
\"source\": {\"type\": \"upload\"},
\"build\": {\"builder\": \"static\"},
\"port\": 3000
}" | jq -r .id)
H="Authorization: Bearer $NEBULAR_TOKEN"
D=$(curl -sS -X POST "$NEBULAR_API/v1/projects/$P/services/$S/deployments" \
-H "$H" -H "Content-Type: application/json" -d '{}' | jq -r .id)
# o destino do rollback precisa ter terminado com sucesso (ready)
delay=2
for i in $(seq 1 40); do
st=$(curl -sS "$NEBULAR_API/v1/projects/$P/services/$S/deployments/$D" -H "$H" | jq -r .status)
echo "tentativa $i: $st"
case "$st" in
ready) break ;;
failed|blocked|canceled|superseded) echo "deploy terminou em $st" >&2; exit 1 ;;
esac
sleep "$delay"; delay=$(( delay * 2 > 30 ? 30 : delay * 2 ))
done
[ "$st" = ready ]
# log do build do deploy D
curl -sS "$NEBULAR_API/v1/projects/$P/services/$S/deployments/$D/logs" -H "$H"
# log de execução do serviço
curl -sS "$NEBULAR_API/v1/projects/$P/services/$S/logs" -H "$H"
# rollback: volta ao deployment anterior do serviço, com as variáveis resolvidas daquela versão
curl -sS -X POST "$NEBULAR_API/v1/projects/$P/services/$S/deployments/$D/rollback" \
-H "$H" -H "Content-Type: application/json" -H "Idempotency-Key: rollback-$D" -d '{}'

O rollback cria um novo deploy; faça o polling dele como no passo 4. Veja Rollback e Logs.