Pular para o conteúdo

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.

  • Token com projects:write, deploy, env:write e logs: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 manifesto linux/arm64 (os servidores são ARM).
  1. Resolve a tag para um digest sha256: (se source.ref já traz @sha256:..., esse digest fixa a versão) e baixa a imagem verificando o hash. A plataforma escolhe a variante linux/arm64 do manifesto; imagem só amd64 é recusada. Tamanho total e número de camadas têm limite; acima disso a imagem é recusada.
  2. Roda sem root: se o USER da 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 em failed com error.code = image_requires_root (a mensagem pede build.user). Não há como rodar como root: declare build.user (uid[:gid]) com um usuário que a imagem suporte, ou escolha uma variante unprivileged.
  3. Sistema de arquivos somente leitura, /tmp em tmpfs de 64 MB, sem capabilities, no-new-privileges. Dados persistentes só num volume.
  4. 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 port igual.

Fixe sempre por digest em produção: veja 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):

Janela do terminal
# mostra o digest do índice (linha "Digest:") e as plataformas disponíveis
docker 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 isolada
crane digest --platform linux/arm64 docker.io/nginxinc/nginx-unprivileged:1.27-alpine

Arquitetura: 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 ...).

Janela do terminal
# DIGEST de exemplo (64 hex): no uso real, o digest obtido acima
DIGEST=sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
SUF=$(date +%s)-$RANDOM
H="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"}
Janela do terminal
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.

Para fonte image, o POST de deploy usa a fonte configurada no serviço (sem corpo).

Janela do terminal
# preparo (passo 1): projeto e serviço de imagem
SUF=$(date +%s)-$RANDOM
H="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=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 ]
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.

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.

Janela do terminal
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"}}'