# 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

- Token com `projects:write`, `deploy`, `env:write` e `logs:read` ([Token de API](/comecar/token/)).
- 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).

## O que a plataforma faz com a imagem

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](/conectados/volumes/).
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](#fixar-por-digest-e-como-obter-o-digest).

## 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):

```bash
# 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 ...`).

```bash
# 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\"}"
```
```json
{"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`

```bash
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\": \"/\"
  }"
```
```json
{"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

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

```bash
# 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](/guia/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](/operacao/deploys-e-status/), [Logs](/operacao/logs/) e [Rollback](/operacao/rollback/).

## 3. Variáveis e atualização

Configure a imagem por variáveis ([Variáveis e referências](/conectados/variaveis-e-referencias/)). 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.

```bash
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"}}'
```
