Rust
Um serviço Rust é compilado com cargo build --release --locked e roda numa imagem mínima (distroless com libc) como não-root.
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 "rust") a Nebular procura Cargo.toml (um Dockerfile, se existir, vence).
Cargo.locké obrigatório no repositório (--locked).- Binário: o
[[bin]]com o nome do pacote; se houver um só[[bin]], ele; senão o nome do pacote. Vários[[bin]]sem nenhum igual ao pacote, ou workspace virtual (sem[package]), exigembuild_commandestart_command. - Versão do toolchain:
rust-versiondoCargo.toml; mais nova que a tabela da plataforma é erro. - Runtime: distroless com libc, sem OpenSSL. Use
rustlsou, se precisar de OpenSSL/bibliotecas do sistema, Dockerfile. - Start padrão:
/app/app. Porta padrão do buildpack: 8080; declareportno serviço.
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.
// src/main.rs (axum 0.8): lê PORT, escuta em 0.0.0.0, expõe /healthzuse axum::{routing::get, Router};
#[tokio::main]async fn main() { let port = std::env::var("PORT").unwrap_or_else(|_| "8080".into()); let app = Router::new() .route("/healthz", get(|| async { "ok" })) .route("/", get(|| async { "olá da Nebular" })); let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{port}")).await.unwrap(); axum::serve(listener, app).await.unwrap();}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-rust-$(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-rust-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 rust. Este bloco é autossuficiente: cria o projeto e o serviço.
NAME="api-rust-$(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\": \"rust\"}, \"port\": 8080, \"healthcheck_path\": \"/healthz\" }"{"id": "01j9zk32py7tcbz2q0ra5aqx7k", "name": "web", "kind": "app", "visibility": "public", "port": 8080}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-rust-$(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\": \"rust\"}, \"port\": 8080, \"healthcheck_path\": \"/healthz\" }" | jq -r .id)
curl -sS -X PUT "$NEBULAR_API/v1/projects/$P/services/$S/variables/RUST_LOG" \ -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 target/ nem 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.
# preparo (passos 1 e 2): projeto e serviço, para o bloco rodar sozinhoNAME="api-rust-$(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\": \"rust\"}, \"port\": 8080, \"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: Cargo.lock / --locked |
Versione o Cargo.lock (rode cargo generate-lockfile e inclua no envio). |
failed: falta de memória (processo morto) |
Compilação Rust é pesada: suba resources.memory_mb do serviço (PATCH no serviço) ou use Dockerfile com cache. |
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-rust-$(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\": \"rust\"}, \"port\": 8080, \"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.
Veja também
Seção intitulada “Veja também”- Rede privada para expor o serviço só ao grupo.
- Dockerfile quando precisar de bibliotecas do sistema.