# PostgreSQL

Um banco gerenciado é um **serviço** `kind=database` do projeto. Ele vive no nó de banco (outro host), **não** na rede privada do grupo: os apps o acessam pela URL com TLS. Escopo do token: `databases:write` (criar, apagar, rotacionar) e `projects:read` (ler). Veja [Tokens e escopos](/seguranca/tokens-e-escopos/).

## 1. Criar

`name` é o nome do serviço (`^[a-z][a-z0-9-]{0,30}$`, único no projeto; é o prefixo das referências `${{db.…}}`). `engine` é `postgres`, `mariadb` ou `sqlite`; `quota_mb` é a cota em MB.

```bash
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\": \"demo-pg-$SUF\"}" | jq -r .id)

curl -sS -X POST "$NEBULAR_API/v1/projects/$P/databases" -H "$H" -H "Content-Type: application/json" \
  -H "Idempotency-Key: pg-db-$SUF" \
  -d '{"name": "db", "engine": "postgres", "quota_mb": 1024}'
```
```json
{"id": "01j9zktv26djn3k6dd2yraqsbt", "name": "db", "kind": "database", "engine": "postgres", "status": "provisioning", "quota_mb": 1024}
```

O banco nasce em `provisioning` e vira `ready`. Consulte `GET /v1/projects/$P/databases/{s}` com backoff (2 s, dobrando até 30 s, até uns 10 min) até o banco estar pronto antes de fazer deploy dos dependentes:

```bash
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\": \"demo-pg-$SUF\"}" | jq -r .id)
DB=$(curl -sS -X POST "$NEBULAR_API/v1/projects/$P/databases" -H "$H" -H "Content-Type: application/json" \
  -H "Idempotency-Key: pg-db-$SUF" \
  -d '{"name": "db", "engine": "postgres", "quota_mb": 1024}' | jq -r .id)

delay=2
for i in $(seq 1 40); do
  st=$(curl -sS "$NEBULAR_API/v1/projects/$P/databases/$DB" -H "$H" | jq -r .status)
  echo "tentativa $i: $st"
  [ "$st" = ready ] && break
  sleep "$delay"; delay=$(( delay * 2 > 30 ? 30 : delay * 2 ))
done
curl -sS "$NEBULAR_API/v1/projects/$P/databases/$DB" -H "$H"
```
```json
{"id": "01j9zktv26djn3k6dd2yraqsbt", "name": "db", "kind": "database", "engine": "postgres", "status": "ready", "quota_mb": 1024, "read_only": false}
```

## 2. Variáveis entregues

Todas **sensíveis** (mascaradas em log e UI) e referenciáveis por qualquer serviço do grupo:

| Variável | Conteúdo |
|---|---|
| `DATABASE_URL` | `postgres://<papel>:<senha>@<public_host>:5432/<db>?sslmode=verify-full` |
| `PGHOST`, `PGPORT`, `PGUSER`, `PGPASSWORD`, `PGDATABASE` | as mesmas peças, separadas |

O certificado do `public_host` é de CA pública (Let's Encrypt): `sslmode=verify-full` funciona sem instalar CA própria. **Não** troque para `sslmode=disable`/`require`.

No app, use por referência (nunca copie o valor): `${{db.DATABASE_URL}}`. Veja [Conexão e referências](/bancos/conexao-e-referencias/).

Ler o valor em claro exige o escopo `env:reveal` num token criado com step-up (`GET …/variables?resolved=true`). Sem ele, o valor vem mascarado. As credenciais também saem de `POST /v1/projects/{p}/databases/{s}/credentials` (exige `env:reveal`): `{"credentials": [{"name": "DATABASE_URL", "value": "..."}, ...]}`.

## 3. Conectar de fora (diagnóstico)

Com as credenciais reveladas, use as variáveis padrão do libpq:

```bash
PGSSLMODE=verify-full psql "$DATABASE_URL" -c 'select 1'
```

## 4. Cota

`quota_mb` é medida a cada 5 minutos. Acima do limite o banco vira `read_only` (escritas falham; há evento e aviso ao time) e **volta ao normal ao baixar** do limite (apague dados). O motor sinaliza `quota_exceeded` (422) quando a cota é incompatível com o uso.

Para mudar a cota: `PATCH /v1/projects/{p}/databases/{s}` com `{"quota_mb": 2048}` (`databases:write`); a resposta é o banco (`quota_mb`, `size_mb`, `read_only`). `GET /v1/projects/{p}/databases` lista os bancos do projeto.

## 5. Rotacionar credencial

`POST /v1/projects/{p}/databases/{s}/rotate` (`databases:write`) gera senha nova e termina as sessões antigas. Os serviços dependentes são marcados "precisam de redeploy" (re-resolução): faça deploy deles.

```bash
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\": \"demo-pg-$SUF\"}" | jq -r .id)
DB=$(curl -sS -X POST "$NEBULAR_API/v1/projects/$P/databases" -H "$H" -H "Content-Type: application/json" \
  -H "Idempotency-Key: pg-db-$SUF" \
  -d '{"name": "db", "engine": "postgres", "quota_mb": 1024}' | jq -r .id)

curl -sS -X POST "$NEBULAR_API/v1/projects/$P/databases/$DB/rotate" \
  -H "$H"
```
```json
{"id": "01j9zktv26djn3k6dd2yraqsbt", "name": "db", "kind": "database", "engine": "postgres"}
```

## 6. Apagar

`DELETE /v1/projects/{p}/databases/{s}` (`databases:write`; `204`). Pela API de token a chamada não exige step-up (o painel exige); por padrão faz **backup final** antes (`{"final_backup": false}` no corpo desliga). Apagar é irreversível para os dados atuais; restaure do backup final se precisar ([Backup e restore](/bancos/backup-restore/)).

## Se algo falhar

| Resposta | O que fazer |
|---|---|
| `401` / `403` | Token ausente/sem escopo `databases:write`. Peça um token com o escopo. |
| `404 not_found` | Projeto ou banco inexistente **ou de outro time** (indistinguíveis). Confira os ids. |
| `409 name_taken` | Já existe um serviço com esse `name` no projeto (todo banco é um serviço). Use outro `name`. |
| `422 quota_exceeded` | Cota insuficiente para o uso atual. Apague dados ou peça cota maior. |
| `503 unavailable` | Nó de banco indisponível. Repita com backoff (a mesma `Idempotency-Key`). Persistindo, pare e avise uma pessoa. |
| `429 rate_limited` | Espere `Retry-After`. |

Tabela completa: [Erros](/erros/tabela/).
