Imagem pública
Use uma imagem pronta quando o software já é distribuído assim (Nginx, Ghost, Metabase). Não há build: a Nebular resolve a imagem, confere e roda.
Pré-requisitos
Seção intitulada “Pré-requisitos”- Token com
projects:write,deploy,env:writeelogs:read(Token de API). - A imagem precisa estar num registry da lista permitida (padrão:
docker.io,ghcr.io,quay.io), ser pública (sem login) e ter manifestolinux/arm64(os servidores são ARM).
O que a plataforma faz com a imagem
Seção intitulada “O que a plataforma faz com a imagem”- Resolve a tag para um digest
sha256:(sesource.refjá traz@sha256:..., esse digest fixa a versão) e baixa a imagem verificando o hash. A plataforma escolhe a variantelinux/arm64do manifesto; imagem sóamd64é recusada. Tamanho total e número de camadas têm limite; acima disso a imagem é recusada. - Roda sem root: se o
USERda imagem for vazio ou root, a plataforma força um uid sem privilégio (65534). Se o app não funcionar assim, o container encerra e o deploy termina emfailedcomerror.code = image_requires_root(a mensagem pedebuild.user). Não há como rodar como root: declarebuild.user(uid[:gid]) com um usuário que a imagem suporte, ou escolha uma variante unprivileged. - Sistema de arquivos somente leitura,
/tmpem tmpfs de 64 MB, sem capabilities,no-new-privileges. Dados persistentes só num volume. - Como não é root, o app não pode escutar portas abaixo de 1024. Use uma imagem que escute em 8080 (ou configure a porta por variável) e declare
portigual.
Fixe sempre por digest em produção: veja Fixar por digest e como obter o digest.
Fixar por digest e como obter o digest
Seção intitulada “Fixar por digest e como obter o digest”A forma aceita pela API é source.ref = "repo[:tag]@sha256:<64 hex>": o digest vai dentro do ref, depois de um @ (o :tag é opcional e só serve de legenda para humanos; quem manda é o digest). Exemplo: docker.io/nginxinc/nginx-unprivileged:1.27-alpine@sha256:0123...cdef. O mesmo formato vale no corpo do deploy, {"image": "repo:tag@sha256:..."}, que atualiza a imagem do serviço e implanta (corpo sem @sha256: é recusado com 400 invalid_request). Não há campo digest separado no contrato. Com só a tag no ref, a plataforma resolve a tag no momento do deploy e registra o digest escolhido em image_digest do deployment.
Como obter o digest de uma tag (rode na sua máquina ou no CI, não é uma rota da Nebular):
# mostra o digest do índice (linha "Digest:") e as plataformas disponíveisdocker buildx imagetools inspect docker.io/nginxinc/nginx-unprivileged:1.27-alpine
# só o digest (índice multi-arch)crane digest docker.io/nginxinc/nginx-unprivileged:1.27-alpine
# digest da variante arm64 isoladacrane digest --platform linux/arm64 docker.io/nginxinc/nginx-unprivileged:1.27-alpineArquitetura: os servidores de produção são linux/arm64. Confira na saída do imagetools inspect que existe uma entrada Platform: linux/arm64. O digest do índice (multi-arch) serve: a plataforma escolhe nele a variante arm64. O digest de uma variante isolada só serve se ela for linux/arm64; o digest de uma variante amd64 falha o deploy com “a imagem não tem variante linux/arm64”. Imagem que você mesmo constrói: publique multi-arch ou só arm64 (docker buildx build --platform linux/arm64 --push ...).
# DIGEST de exemplo (64 hex): no uso real, o digest obtido acimaDIGEST=sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdefSUF=$(date +%s)-$RANDOMH="Authorization: Bearer $NEBULAR_TOKEN"P=$(curl -sS -X POST "$NEBULAR_API/v1/projects" -H "$H" -H "Content-Type: application/json" \ -d "{\"name\": \"digest-$SUF\"}" | jq -r .id)S=$(curl -sS -X PATCH "$NEBULAR_API/v1/projects/$P/services/web" -H "$H" -H "Content-Type: application/json" \ -d "{\"port\": 8080, \"source\": {\"type\": \"image\", \"ref\": \"docker.io/nginxinc/nginx-unprivileged:1.27-alpine@$DIGEST\"}}" | jq -r .id)curl -sS -X POST "$NEBULAR_API/v1/projects/$P/services/$S/deployments" -H "$H" \ -H "Content-Type: application/json" -H "Idempotency-Key: digest-$SUF" \ -d "{\"image\": \"docker.io/nginxinc/nginx-unprivileged:1.27-alpine@$DIGEST\"}"{"id": "01j9zkv1t9ndd76xmr8ahxee7p", "service_id": "01j9zk32py7tcbz2q0ra5aqx7k", "status": "queued", "trigger": "api", "source_ref": "docker.io/nginxinc/nginx-unprivileged:1.27-alpine@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "image_digest": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"}1. Criar o serviço com source.type = image
Seção intitulada “1. Criar o serviço com source.type = image”NAME="vitrine-$(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\": \"image\", \"ref\": \"docker.io/nginxinc/nginx-unprivileged:1.27-alpine\"}, \"port\": 8080, \"healthcheck_path\": \"/\" }"{"id": "01j9zk32py7tcbz2q0ra5aqx7k", "name": "web", "kind": "app", "visibility": "public", "port": 8080}| Resposta | O que fazer |
|---|---|
400 invalid_request |
source.type deve ser upload, git ou image; confira ref (formato registro/org/imagem:tag, ou registro/org/imagem:tag@sha256:<digest>). |
404 |
Projeto inexistente ou de outro time (indistinguíveis). |
409 name_taken / slug_taken |
Nome de serviço ou slug já em uso. |
403 |
Falta projects:write. |
2. Deploy e polling
Seção intitulada “2. Deploy e polling”Para fonte image, o POST de deploy usa a fonte configurada no serviço (sem corpo).
# preparo (passo 1): projeto e serviço de imagemSUF=$(date +%s)-$RANDOMH="Authorization: Bearer $NEBULAR_TOKEN"P=$(curl -sS -X POST "$NEBULAR_API/v1/projects" -H "$H" -H "Content-Type: application/json" \ -d "{\"name\": \"vitrine-$SUF\"}" | jq -r .id)S=$(curl -sS -X PATCH "$NEBULAR_API/v1/projects/$P/services/web" -H "$H" -H "Content-Type: application/json" \ -d '{"source": {"type": "image", "ref": "ghcr.io/org/app:1.0"}}' | jq -r .id)
# fonte image: o POST de deploy usa a imagem configurada no serviço (corpo {})D=$(curl -sS -X POST "$NEBULAR_API/v1/projects/$P/services/$S/deployments" -H "$H" \ -H "Content-Type: application/json" -H "Idempotency-Key: deploy-nginx-$SUF" -d '{}' | jq -r .id)
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 ]| Estado ou erro | O que fazer |
|---|---|
failed com error.code = image_requires_root |
A imagem só funciona como root. Declare build.user (uid[:gid], nunca root) no serviço, ou troque por uma variante sem privilégio (-unprivileged, -rootless, distroless) ou use Dockerfile para ajustar permissões. |
failed: registry fora da lista permitida |
Use docker.io, ghcr.io ou quay.io (ou peça ao operador para incluir o registry). |
failed: “a imagem não tem variante linux/arm64” |
Escolha outra tag ou uma imagem multi-arch com linux/arm64; um digest amd64 não serve. |
failed: imagem maior que o limite / camadas demais |
Use uma variante menor (alpine, slim). |
failed: digest não confere / imagem não encontrada pelo digest |
O digest no ref não existe no registry ou não bate com o conteúdo; obtenha-o de novo (veja acima). |
failed com o app caindo logo após subir |
Leia o log de execução (GET .../services/$S/logs): porta errada, escrita em disco somente leitura (mova para /tmp ou volume). |
503 |
Transitório: repita com backoff. |
Veja também Deploys e status, Logs e Rollback.
3. Variáveis e atualização
Seção intitulada “3. Variáveis e atualização”Configure a imagem por variáveis (Variáveis e referências). Para atualizar a versão, mude source.ref (nova tag e novo @sha256:...) com PATCH no serviço e faça um novo deploy; o rollback volta ao deployment anterior.
NAME="vitrine-$(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\": \"private\", \"source\": {\"type\": \"image\", \"ref\": \"docker.io/nginxinc/nginx-unprivileged:1.27-alpine\"}, \"port\": 8080}" | jq -r .id)
curl -sS -X PATCH "$NEBULAR_API/v1/projects/$P/services/$S" \ -H "Authorization: Bearer $NEBULAR_TOKEN" -H "Content-Type: application/json" \ -d '{"source": {"type": "image", "ref": "docker.io/nginxinc/nginx-unprivileged:1.27.5-alpine"}}'