Python (FastAPI)
Um serviço Python instala as dependências com pip e roda como não-root numa imagem python slim. Este guia usa FastAPI; Flask e Django também são detectados.
Pré-requisitos
Seção intitulada “Pré-requisitos”- Token com os escopos
projects:write,deploy,env:writeelogs:read(veja Token de API e Tokens e escopos). Para listar projetos e serviços,projects:read. NEBULAR_APIeNEBULAR_TOKENno ambiente;curlejqinstalados.- Toda chamada que cria algo leva
Idempotency-Key: repetir após timeout é seguro.
Como a Nebular constrói este tipo de app
Seção intitulada “Como a Nebular constrói este tipo de app”Com builder: "auto" (ou "python") a Nebular procura pyproject.toml, requirements.txt ou Pipfile (um Dockerfile, se existir, vence).
- FastAPI/Starlette/uvicorn: start
python -m uvicorn <modulo>:app --host 0.0.0.0 --port 8000, onde o módulo é achado emmain.py,app.py,server.py,app/main.pyousrc/main.py(o arquivo precisa conterFastAPI(). Não achou: definastart_command. - Flask:
gunicorn <modulo>:app; Django:gunicorn <pacote>.wsgi:application(exigemanage.py). Sem framework web:python main.pyoupython app.py. - Dependências:
requirements.txt(pip install -r),Pipfileoupyproject.toml(pip install .), instaladas em/app/deps(PYTHONPATHjá ajustado). Pacotes nativos que precisem de compilador não funcionam na imagem slim: use Dockerfile. - Porta padrão do buildpack: 8000; declare
portno serviço (use o mesmo valor emPORT).
Regras comuns a todas as stacks:
- O app precisa escutar em
0.0.0.0na porta que o serviço declara emporte que a plataforma também entrega na variávelPORT. LeiaPORTdo ambiente; declareportno serviço com o mesmo valor. - O container roda sem root, com sistema de arquivos somente leitura e
/tmpem 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 emNEXT_PUBLIC_*ou equivalente de bundle de browser.
from fastapi import FastAPI
app = FastAPI()
@app.get("/healthz")def healthz(): return {"ok": True}
@app.get("/")def raiz(): return {"msg": "olá da Nebular"}fastapiuvicornFixe versões em requirements.txt para builds reproduzíveis. O start padrão escuta na porta 8000; para outra porta, defina start_command com --port e port igual.
1. Criar o projeto
Seção intitulada “1. Criar o projeto”O projeto é o grupo: todos os seus serviços compartilham uma rede privada. Guarde o id da resposta.
NAME="api-py-$(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": "api-py-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. |
2. Criar o serviço
Seção intitulada “2. Criar o serviço”O serviço web é público (visibility: "public" exige um slug único) e usa fonte upload e o builder python. Este bloco é autossuficiente: cria o projeto e o serviço.
NAME="api-py-$(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\": \"python\"}, \"port\": 8000, \"healthcheck_path\": \"/healthz\" }"{"id": "01j9zk32py7tcbz2q0ra5aqx7k", "name": "web", "kind": "app", "visibility": "public", "port": 8000}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. |
3. Variáveis
Seção intitulada “3. Variáveis”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.
NAME="api-py-$(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\": \"python\"}, \"port\": 8000, \"healthcheck_path\": \"/healthz\" }" | jq -r .id)
curl -sS -X PUT "$NEBULAR_API/v1/projects/$P/services/$S/variables/LOG_LEVEL" \ -H "Authorization: Bearer $NEBULAR_TOKEN" -H "Content-Type: application/json" \ -d '{"value": "info"}'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. |
4. Enviar o código, fazer o deploy e esperar
Seção intitulada “4. Enviar o código, fazer o deploy e esperar”Compacte a pasta do projeto (sem .venv, .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.
# preparo (passos 1 e 2): projeto e serviço, para o bloco rodar sozinhoNAME="api-py-$(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\": \"python\"}, \"port\": 8000, \"healthcheck_path\": \"/healthz\" }" | jq -r .id)H="Authorization: Bearer $NEBULAR_TOKEN"
# código: a pasta do projeto, sem node_modules, .git nem .envARQ=$(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=2for 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_urlcurl -sS "$NEBULAR_API/v1/projects/$P/services/$S" -H "$H" | tee /dev/stderr | jq -e '.public_url | startswith("https://")' >/dev/nullCom 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: “app (FastAPI/Flask) não encontrado” / “comando de start desconhecido” |
Defina start_command (por exemplo python -m uvicorn pacote.app:app --host 0.0.0.0 --port 8000). |
failed ao instalar uma dependência |
A imagem não tem compilador: use uma roda binária (wheel) ou Dockerfile. |
5. Logs e rollback
Seção intitulada “5. Logs e rollback”Log do build (deploys failed) e log de execução, ambos com paginação por cursor; precisam do escopo logs:read.
# preparo (passos 1 e 2): projeto e serviço, para o bloco rodar sozinhoNAME="api-py-$(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\": \"python\"}, \"port\": 8000, \"healthcheck_path\": \"/healthz\" }" | 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=2for 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 Dcurl -sS "$NEBULAR_API/v1/projects/$P/services/$S/deployments/$D/logs" -H "$H"
# log de execução do serviçocurl -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ãocurl -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.