docs: Passo 5 validado em produção (v1.0.16) — modelo Build Once completo
- VERDADE-HOJE.md e DEPLOY-PRODUCAO.md: produção v1.0.16 (commit 471c2c2);
tag promove por re-tag SEM rebuild (digest :v1.0.16 == :471c2c2, provado);
versão nasce da tag; constants.ts/update-version.js removidos. Passo 5 ✅.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+48
-73
@@ -1,33 +1,29 @@
|
|||||||
# Deploy & Versionamento — Runbook ScoreOdonto
|
# Deploy & Versionamento — Runbook ScoreOdonto
|
||||||
|
|
||||||
> Auditado contra o sistema em **17/jun/2026**. Cada item carrega seu **status real** — o doc
|
> Auditado contra o sistema em **17/jun/2026**. Cada item carrega seu **status real**.
|
||||||
> responde *"o que existe hoje?"*, não *"o que gostaríamos?"*.
|
|
||||||
>
|
>
|
||||||
> **Legenda de status (convenção do projeto):**
|
> **Legenda:** ✅ EM PRODUÇÃO · 🧪 EM DEV (não promovido) · 🎯 ALVO (não implementado).
|
||||||
> - ✅ **EM PRODUÇÃO** — está no ar em `scoreodonto.com` (VPS1), verificável agora.
|
|
||||||
> - 🧪 **EM DEV** — implementado no working tree da VPS4 / `dev.scoreodonto.com`; **não commitado, não promovido**.
|
|
||||||
> - 🎯 **ALVO** — desenhado/aprovado, **ainda não implementado**.
|
|
||||||
>
|
>
|
||||||
> **Produção AGORA:** `v1.0.15 · commit f2eb90c · 2026-06-17` (`curl https://scoreodonto.com/api/version`).
|
> **Produção AGORA:** `v1.0.16 · commit 471c2c2 · 2026-06-17` (`curl https://scoreodonto.com/api/version`).
|
||||||
> Os Passos 1/3/4 (versão em runtime) foram **promovidos a produção** em 17/jun (tag `v1.0.15`).
|
> Modelo **Build Once → Promote → Deploy** completo — Passo 5 validado em v1.0.16. Tudo abaixo ✅.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. O modelo
|
## 1. O modelo ✅
|
||||||
|
|
||||||
> **Push** na `main` produz imagem por **commit** no registry; a **tag `vX.Y.Z`** **promove** essa imagem para produção. **Produção nunca builda — só executa imagens.**
|
> **Push** na `main` builda **uma vez** e publica por **commit**; a **tag `vX.Y.Z`** **re-tagueia** esse mesmo artefato (sem rebuild) e deploya. **Produção nunca builda — só executa imagens.**
|
||||||
|
|
||||||
```
|
```
|
||||||
PUSH main (commit C) TAG vX.Y.Z (no commit C)
|
PUSH main (commit C) TAG vX.Y.Z (no commit C)
|
||||||
│ │
|
│ │
|
||||||
▼ ▼
|
▼ ▼
|
||||||
CI: build CI: deploy
|
CI: build (1x) CI: promote (SEM build) ✅
|
||||||
publica :C, :<versão>, :latest ✅ hoje: REBUILDA + promove + deploya
|
publica :C + :latest re-tag :C → :vX.Y.Z (MESMO digest) + deploy
|
||||||
▼ 🎯 alvo (Passo 5): só re-tag :C→:vX.Y.Z, SEM build
|
▼ ▼
|
||||||
registry ▼
|
registry VPS1: pull :vX.Y.Z + up --no-build
|
||||||
VPS1: pull + up --no-build ✅
|
APP_VERSION=tag injetada em runtime
|
||||||
APP_VERSION=tag em runtime ✅ (Passo 3, em prod)
|
|
||||||
```
|
```
|
||||||
|
Prova (v1.0.16): `:v1.0.16` tem **digest idêntico** a `:471c2c2` — a tag é só um rótulo sobre a imagem já testada.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -37,15 +33,14 @@
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Infra VPS1/3/4 + Docker rootless | ✅ | Mapa na §3 |
|
| Infra VPS1/3/4 + Docker rootless | ✅ | Mapa na §3 |
|
||||||
| Registry (`git.clube67.com/ruicesar/...`) | ✅ | Ver `REGISTRY.md` |
|
| Registry (`git.clube67.com/ruicesar/...`) | ✅ | Ver `REGISTRY.md` |
|
||||||
| CI: `act_runner` "vps4-builder" builda+publica em push | ✅ | `.gitea/workflows/build.yml`; runner systemd na VPS4 |
|
| CI: job `build` publica por **commit** em push (sem `APP_VERSION`) | ✅ | `.gitea/workflows/build.yml` (runner `vps4-builder`) |
|
||||||
| Deploy por tag (job `deploy` SSH VPS4→VPS1) | ✅ | `deploy-prod.sh` na VPS1 |
|
| Job `promote` por tag: re-tag `:<commit>→:vX.Y.Z` (sem rebuild) + deploy | ✅ | validado em v1.0.16 (`:v1.0.16` == `:471c2c2`) |
|
||||||
|
| Validação de integridade no promote (commit existe? digest igual? front+back mesmo commit) | ✅ | falha se o commit não foi pushado |
|
||||||
| Produção não builda (`image:` + `pull` + `up --no-build`) | ✅ | `docker-compose.prod.yml` |
|
| Produção não builda (`image:` + `pull` + `up --no-build`) | ✅ | `docker-compose.prod.yml` |
|
||||||
| Banco DEV isolado em `pgdev`; prod no Postgres da VPS3 | ✅ | Ver `BANCO-DEV-ESTRATEGIA.md` |
|
| Banco DEV isolado em `pgdev`; prod no Postgres da VPS3 | ✅ | Ver `BANCO-DEV-ESTRATEGIA.md` |
|
||||||
| `GET /api/version` responde (de `process.env`) | ✅ | No ar: v1.0.15 · f2eb90c · PROD |
|
| Versão em runtime (`/api/version`) → Configurações + telas públicas | ✅ | `useAppVersion()`; sem `APP_VERSION` manual |
|
||||||
| **Versão exibida em Configurações → "Sobre o sistema"** | ✅ | Passo 1 (promovido em v1.0.15; saiu do Sidebar) |
|
| `APP_VERSION`/`APP_ENV` injetados no deploy em runtime | ✅ | `compose.prod` `environment` |
|
||||||
| **Front/telas consomem `/api/version` (`useAppVersion`); app não usa `APP_VERSION`** | ✅ | Passos 1/4 (em prod desde v1.0.15) |
|
| Versão **nasce da tag git** (`constants.ts`/`update-version.js` removidos) | ✅ | Passo 5 |
|
||||||
| **Deploy injeta `APP_VERSION`/`APP_ENV` em runtime (`environment`)** | ✅ | Passo 3 (`compose.prod`/`deploy-prod.sh` em prod) |
|
|
||||||
| **Tag promove sem rebuild + validação de integridade** | 🎯 | Passo 5 (§7) — único item pendente |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -53,90 +48,70 @@
|
|||||||
|
|
||||||
| Onde | Host | Papel | Docker |
|
| Onde | Host | Papel | Docker |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| VPS1 | `10.99.0.1` | **Produção** (Traefik + apps) | rootless (`unix:///run/user/1000/docker.sock`) |
|
| VPS1 | `10.99.0.1` | **Produção** (Traefik + apps) | rootless |
|
||||||
| VPS3 | `10.99.0.3` | Postgres (prod) + Gitea + registry | — |
|
| VPS3 | `10.99.0.3` | Postgres (prod) + Gitea + registry | — |
|
||||||
| VPS4 | `10.99.0.4` | **Dev + Builder** (código; runner CI; `pgdev`) | rootless |
|
| VPS4 | `10.99.0.4` | **Dev + Builder** (código; runner CI; `pgdev`) | rootless |
|
||||||
|
|
||||||
- Repo (fonte da verdade): `ssh://git@10.99.0.3/ruicesar/scoreodonto.com.git`.
|
- Repo: `ssh://git@10.99.0.3/ruicesar/scoreodonto.com.git`. Project Compose: `scoreodontocom`.
|
||||||
- Project Compose: `scoreodontocom`. Diretório nas duas máquinas: `/home/deploy/stack/scoreodonto.com`.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. De onde vem a versão (contrato `/api/version`)
|
## 4. De onde vem a versão (`/api/version`)
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{ "name": "ScoreOdonto API", "version": "v1.0.15", "commit": "f2eb90c",
|
{ "name": "ScoreOdonto API", "version": "v1.0.16", "commit": "471c2c2",
|
||||||
"build": "2026-06-17 03:37 UTC", "environment": "PROD" }
|
"build": "2026-06-17 10:35 UTC", "environment": "PROD" }
|
||||||
```
|
```
|
||||||
- **commit / build** = carimbados no **build** da imagem (imutáveis — identidade do artefato). ✅
|
- **commit / build** = carimbados no **build** (imutáveis — identidade do artefato).
|
||||||
- **version / environment** = **injetados no deploy em runtime** (a partir da tag) via `environment` do `compose.prod` ✅ (Passo 3, em prod) — a mesma imagem pode ser promovida sob qualquer tag sem rebuild.
|
- **version / environment** = **injetados no deploy em runtime** (a partir da tag). A mesma imagem é promovida sob qualquer tag, sem rebuild.
|
||||||
- **Frontend** = lê tudo de `/api/version` via `useAppVersion()`; **não embute mais a versão** ✅ (Passos 1/4, em prod). Fonte única = backend.
|
- **Frontend** lê tudo de `/api/version` (`useAppVersion()`); não embute versão. Fonte única = backend.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. Runbook — subir um release (fluxo ATUAL ✅)
|
## 5. Runbook — subir um release ✅
|
||||||
|
|
||||||
> ⚠️ **Regra de ouro:** confirme **DEV ou PRODUÇÃO** antes de agir. `dev.scoreodonto.com` = VPS4 (banco `pgdev`, descartável). `scoreodonto.com` = VPS1 (real). **Nunca** `git push --force` na `main`. Push/tag exigem autorização explícita.
|
> ⚠️ **Regra de ouro:** confirme **DEV ou PRODUÇÃO**. `dev.scoreodonto.com` = VPS4 (banco `pgdev`). `scoreodonto.com` = VPS1 (real). **Nunca** `git push --force` na `main`. Push/tag exigem autorização explícita.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1) VPS4: validar em DEV (build local do container de dev)
|
# 1) VPS4: validar em DEV
|
||||||
cd /home/deploy/stack/scoreodonto.com
|
cd /home/deploy/stack/scoreodonto.com
|
||||||
node --check backend/server.js
|
node --check backend/server.js
|
||||||
docker compose build scoreodonto-backend scoreodonto-frontend
|
docker compose build scoreodonto-backend scoreodonto-frontend
|
||||||
docker compose up -d --force-recreate scoreodonto-backend scoreodonto-frontend
|
docker compose up -d --force-recreate scoreodonto-backend scoreodonto-frontend # https://dev.scoreodonto.com
|
||||||
# conferir em https://dev.scoreodonto.com
|
|
||||||
|
|
||||||
# 2) ⚠️ transitório (até Passo 5): a TAG precisa casar com frontend/constants.ts
|
# 2) commit + push → CI builda 1x e publica :<commit> + :latest (NÃO deploya)
|
||||||
node update-version.js # bump (ex. V1.0.14 → V1.0.15)
|
git add -A && git commit -m "feat: <descrição>"
|
||||||
|
|
||||||
# 3) commit + push → CI builda e publica :<commit> + :<versão> + :latest (NÃO deploya)
|
|
||||||
git add -A && git commit -m "feat: <descrição> (vX.Y.Z)"
|
|
||||||
git push origin main
|
git push origin main
|
||||||
|
|
||||||
# 4) tag = "aprovado p/ produção" → CI (hoje REBUILDA ✅) promove + deploya na VPS1
|
# 3) tag = "aprovado p/ produção" → job PROMOTE re-tagueia :<commit>→:vX.Y.Z (SEM rebuild) + deploya
|
||||||
git tag v1.0.15 && git push origin v1.0.15
|
# (o número da versão é livre — não precisa casar com nenhum arquivo)
|
||||||
|
git tag -a vX.Y.Z -m "release vX.Y.Z" && git push origin vX.Y.Z
|
||||||
|
|
||||||
# 5) verificar SEM SSH:
|
# 4) verificar (sem SSH)
|
||||||
curl -s https://scoreodonto.com/api/version # → version/commit/environment
|
curl -s https://scoreodonto.com/api/version # → vX.Y.Z · <commit> · PROD
|
||||||
```
|
```
|
||||||
|
|
||||||
**Backup do banco de PRODUÇÃO antes de releases sensíveis** (migrações rodam no boot; aditivas/idempotentes):
|
**Backup do banco de PRODUÇÃO antes de releases sensíveis** (via container, pois não há shell na VPS3):
|
||||||
```bash
|
```bash
|
||||||
ssh deploy@10.99.0.3 'pg_dump -Fc scoreodonto -f ~/backups/scoreodonto_$(date +%Y%m%d_%H%M).dump'
|
docker run --rm -e PGPASSWORD=<senha> -v /home/deploy/backups:/b postgres:18 \
|
||||||
|
pg_dump -h 10.99.0.3 -U scoreodonto_user -d scoreodonto -Fc -f /b/scoreodonto_$(date +%Y%m%d_%H%M).dump
|
||||||
```
|
```
|
||||||
|
|
||||||
> ### ⚠️ Fase transitória — por que `APP_VERSION`/`constants.ts` ainda existem
|
|
||||||
> Estamos entre o **modelo antigo** (versão escrita à mão em `constants.ts`) e o **novo**
|
|
||||||
> (identidade pelo **commit**; tags só promovem). Até o **Passo 5**, o CI ainda lê `APP_VERSION`
|
|
||||||
> para nomear a imagem → o bump manual continua **necessário para evitar colisão de tags no
|
|
||||||
> registry** (republicar `:vX.Y.Z` com outro código quebraria *"uma tag = um artefato"*).
|
|
||||||
> **Após o Passo 5:** `constants.ts` e `update-version.js` são removidos, a identidade passa a
|
|
||||||
> ser o commit, e tags apenas **promovem** artefatos já construídos.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. Rollback ✅
|
## 6. Rollback ✅
|
||||||
|
|
||||||
Reverter o **código** é seguro (migrações só adicionam). Por imagem, **sem rebuild**:
|
Por imagem, **sem rebuild** — na VPS1:
|
||||||
```bash
|
```bash
|
||||||
ssh deploy@10.99.0.1
|
/home/deploy/stack/scoreodonto.com/deploy-prod.sh <tag-ou-commit-anterior> # pull + up --no-build
|
||||||
/home/deploy/stack/scoreodonto.com/deploy-prod.sh v1.0.13 # tag anterior → pull + up --no-build
|
|
||||||
```
|
```
|
||||||
Restaurar o dump do banco só em caso extremo (`pg_restore --clean`).
|
⚠️ A referência confiável é o **commit** (`:<sha>`). Tags `:vX.Y.Z` antigas (pré-Passo 5) podem ter sido republicadas com código diferente — a partir do Passo 5, `:vX.Y.Z` é sempre um alias imutável do commit. Migrações são aditivas (voltar o código é seguro). Dump em `/home/deploy/backups/` só em extremo.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. Passo 5 — "tag promove, não builda" 🎯
|
## 7. Follow-ups (não bloqueiam) 🎯
|
||||||
|
- `GET /api/health` (monitoramento/diagnóstico).
|
||||||
|
- **Política de retenção** do registry (uma imagem `:<commit>` por push acumula).
|
||||||
|
- Reconciliar o repo na VPS1 (working tree sujo, não afeta o deploy por imagem).
|
||||||
|
|
||||||
Desenho **aprovado (17/jun/2026), não aplicado**. Refina o CI existente para Build Once → Promote → Deploy:
|
Racional do modelo em `deploy.md`; histórico em `project-cicd-tag-promove-nao-rebuilda` (memória).
|
||||||
- `build.yml` → 2 jobs por ref: **build** (só push; publica `:<commit>`+`:latest`, sem `APP_VERSION`); **promote** (só tag; re-tag `:<commit>→:vX.Y.Z` via `docker buildx imagetools create`, sem rebuild) → deploy.
|
|
||||||
- **Validação de integridade antes do deploy:** existe `backend:<C>` e `frontend:<C>`? digests válidos? alias `:vX.Y.Z` == digest de `:<C>`? front e back do **mesmo commit**? Só então deploy (evita deploy parcial).
|
|
||||||
- Decisões fechadas: manter **`:<commit>`** (sem prefixo); **falhar** se o commit não foi pushado (nunca buildar na tag); **`:latest`** só conveniência.
|
|
||||||
- Eliminar `update-version.js` + `frontend/constants.ts` (a versão passa a nascer da **tag git**).
|
|
||||||
- Follow-ups: `GET /api/health`; **política de retenção** do registry.
|
|
||||||
|
|
||||||
Racional completo em `deploy.md` e na memória `project-cicd-tag-promove-nao-rebuilda`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
> **Sequência acordada para fechar o drift:** (1) doc honesta com status ✅/🧪/🎯 [este doc] → (2) commitar Passos 1/3/4 → (3) promover → (4) confirmar `/api/version` → (5) elevar `VERDADE-HOJE.md` a doc principal (com esta convenção e precedência) → (6) Passo 5 → (7) atualizar docs. À medida que cada item 🧪 for promovido, vira ✅ aqui.
|
|
||||||
|
|||||||
+7
-7
@@ -9,8 +9,8 @@
|
|||||||
### 1. Onde está o código?
|
### 1. Onde está o código?
|
||||||
- **Fonte da verdade:** Gitea na **VPS3**, repo `ruicesar/scoreodonto.com`.
|
- **Fonte da verdade:** Gitea na **VPS3**, repo `ruicesar/scoreodonto.com`.
|
||||||
- **Dev (trabalho):** VPS4 em `/home/deploy/stack/scoreodonto.com`.
|
- **Dev (trabalho):** VPS4 em `/home/deploy/stack/scoreodonto.com`.
|
||||||
- **Produção:** VPS1 roda **imagens versionadas do registry** (não código-fonte). No ar: **v1.0.15 · commit `f2eb90c`**.
|
- **Produção:** VPS1 roda **imagens versionadas do registry** (não código-fonte). No ar: **v1.0.16 · commit `471c2c2`**.
|
||||||
- **Documentação:** centralizada em `scoreodonto.com/documentação/`.
|
- **Documentação:** centralizada em `scoreodonto.com/doc/` (pasta única).
|
||||||
|
|
||||||
### 2. Onde está o banco?
|
### 2. Onde está o banco?
|
||||||
- **Produção:** PostgreSQL no host da **VPS3** (`10.99.0.3:5432`), banco `scoreodonto`, user `scoreodonto_user`.
|
- **Produção:** PostgreSQL no host da **VPS3** (`10.99.0.3:5432`), banco `scoreodonto`, user `scoreodonto_user`.
|
||||||
@@ -25,12 +25,12 @@
|
|||||||
### 4. Quem faz deploy?
|
### 4. Quem faz deploy?
|
||||||
- **CI/CD por tag (Gitea Actions).** Push na `main` → o runner `act_runner` "vps4-builder" (VPS4, rootless) builda e **publica no registry** (NÃO deploya). `git tag vX.Y.Z && git push` → CI + job `deploy` promove para a VPS1 (`deploy-prod.sh`).
|
- **CI/CD por tag (Gitea Actions).** Push na `main` → o runner `act_runner` "vps4-builder" (VPS4, rootless) builda e **publica no registry** (NÃO deploya). `git tag vX.Y.Z && git push` → CI + job `deploy` promove para a VPS1 (`deploy-prod.sh`).
|
||||||
- **Produção consome imagem** (`pull` + `up -d --no-build`).
|
- **Produção consome imagem** (`pull` + `up -d --no-build`).
|
||||||
- ⚠️ Hoje a **tag rebuilda** antes de promover; o **Passo 5** mudará para a tag apenas **re-taguear** o artefato já construído por commit.
|
- ✅ **Build Once → Promote (Passo 5, validado em produção):** push builda 1× e publica por **commit** (`:<sha>`); a **tag re-tagueia** esse artefato como `:vX.Y.Z` (sem rebuild — digest idêntico) e deploya. Provado em v1.0.16 (`:v1.0.16` == `:471c2c2`, mesmo digest).
|
||||||
|
|
||||||
### 5. Como versiona?
|
### 5. Como versiona?
|
||||||
- A versão exibida vem do **backend em runtime**: `GET /api/version` (de `process.env`), mostrada em **Configurações → "Sobre o sistema"** e nas telas públicas (o frontend consome via `useAppVersion()`, não embute mais a versão). No ar: **v1.0.15 · `f2eb90c` · PROD**.
|
- A versão exibida vem do **backend em runtime**: `GET /api/version` (de `process.env`), mostrada em **Configurações → "Sobre o sistema"** e nas telas públicas (o frontend consome via `useAppVersion()`, não embute mais a versão). No ar: **v1.0.16 · `471c2c2` · PROD**.
|
||||||
- No deploy, `APP_VERSION` (= tag) e `APP_ENV=PROD` são **injetados em runtime**; `commit`/`build` vêm carimbados na imagem.
|
- A versão **nasce da tag git** e é **injetada no deploy em runtime** (`APP_VERSION`=tag, `APP_ENV=PROD`); `commit`/`build` vêm carimbados na imagem.
|
||||||
- ⚠️ **Legado transitório:** `frontend/constants.ts` (`APP_VERSION`) + `update-version.js` ainda existem **só** para o CI nomear a imagem (bump manual evita colisão de tag). Serão **eliminados no Passo 5** (a versão nascerá da tag git).
|
- ✅ **`constants.ts`/`update-version.js` foram removidos** (Passo 5) — não há mais versão manual.
|
||||||
|
|
||||||
### 6. Como volta uma versão (rollback)?
|
### 6. Como volta uma versão (rollback)?
|
||||||
- **Por imagem, sem rebuild:** na VPS1, `./deploy-prod.sh <ref-anterior>` → `pull` + `up -d --no-build`.
|
- **Por imagem, sem rebuild:** na VPS1, `./deploy-prod.sh <ref-anterior>` → `pull` + `up -d --no-build`.
|
||||||
@@ -39,4 +39,4 @@
|
|||||||
---
|
---
|
||||||
|
|
||||||
## Veredito de uma linha
|
## Veredito de uma linha
|
||||||
> O código vive no Gitea (VPS3); a **produção (VPS1) roda imagens versionadas do registry** (hoje **v1.0.15**), sem buildar; o **DEV está isolado no `pgdev`** (não toca o banco de produção). **CI/CD por tag ativo**, com **versão visível em runtime** (`/api/version` → Configurações). Pendência técnica principal: **Passo 5** (tag promove sem rebuild; eliminar o `APP_VERSION` manual).
|
> O código vive no Gitea (VPS3); a **produção (VPS1) roda imagens versionadas do registry** (hoje **v1.0.16**), sem buildar; o **DEV está isolado no `pgdev`** (não toca o banco de produção). **CI/CD por tag ativo** no modelo **Build Once → Promote → Deploy** (push builda por commit; tag re-tagueia sem rebuild e deploya — validado em v1.0.16), com **versão visível em runtime** (`/api/version` → Configurações) nascendo da tag git.
|
||||||
|
|||||||
Reference in New Issue
Block a user