# Deploy & Versionamento — Runbook ScoreOdonto > Auditado contra o sistema em **17/jun/2026**. Cada item carrega seu **status real** — o doc > responde *"o que existe hoje?"*, não *"o que gostaríamos?"*. > > **Legenda de status (convenção do projeto):** > - ✅ **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.14 · commit 081985c · 2026-06-16` (`curl https://scoreodonto.com/api/version`). > Todo o trabalho de versionamento abaixo marcado 🧪 está no working tree da VPS4 (não commitado). --- ## 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 main (commit C) TAG vX.Y.Z (no commit C) │ │ ▼ ▼ CI: build CI: deploy publica :C, :, :latest ✅ hoje: REBUILDA + promove + deploya ▼ 🎯 alvo (Passo 5): só re-tag :C→:vX.Y.Z, SEM build registry ▼ VPS1: pull + up --no-build ✅ APP_VERSION=tag em runtime 🧪 (Passo 3) ``` --- ## 2. Estado por componente (verificado) | Componente | Status | Detalhe | |---|---|---| | Infra VPS1/3/4 + Docker rootless | ✅ | Mapa na §3 | | 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 | | Deploy por tag (job `deploy` SSH VPS4→VPS1) | ✅ | `deploy-prod.sh` na VPS1 | | 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` | | `GET /api/version` responde (de `process.env`) | ✅ | Em prod, valores vêm do **build** da imagem | | **Versão exibida em Configurações → "Sobre o sistema"** | 🧪 | Passo 1. Em **produção a versão está no Sidebar** | | **Front/telas consomem `/api/version` (`useAppVersion`); app não usa `APP_VERSION`** | 🧪 | Passos 1/4. Em **prod o front usa `APP_VERSION` do bundle** | | **Deploy injeta `APP_VERSION`/`APP_ENV` em runtime (`environment`)** | 🧪 | Passo 3 (`compose.prod`/`deploy-prod.sh` no working tree) | | **Tag promove sem rebuild + validação de integridade** | 🎯 | Passo 5 (§7) | --- ## 3. Mapa de máquinas ✅ | Onde | Host | Papel | Docker | |---|---|---|---| | VPS1 | `10.99.0.1` | **Produção** (Traefik + apps) | rootless (`unix:///run/user/1000/docker.sock`) | | VPS3 | `10.99.0.3` | Postgres (prod) + Gitea + registry | — | | 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`. - Project Compose: `scoreodontocom`. Diretório nas duas máquinas: `/home/deploy/stack/scoreodonto.com`. --- ## 4. De onde vem a versão (contrato `/api/version`) ```json { "name": "ScoreOdonto API", "version": "v1.0.14", "commit": "081985c", "build": "2026-06-16 08:06 UTC", "environment": "PROD" } ``` - **commit / build** = carimbados no **build** da imagem (imutáveis — identidade do artefato). ✅ - **version / environment** — **hoje (✅)** também vêm do build da imagem (build arg no CI). **🧪 (Passo 3):** passam a ser injetados no **deploy** em runtime (a partir da tag), liberando a mesma imagem para qualquer tag sem rebuild. - **Frontend** — **🧪 (Passos 1/4):** lê tudo de `/api/version` via `useAppVersion()`; deixa de embutir versão. **Em produção** ainda embute `APP_VERSION` no bundle. --- ## 5. Runbook — subir um release (fluxo ATUAL ✅) > ⚠️ **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. ```bash # 1) VPS4: validar em DEV (build local do container de dev) cd /home/deploy/stack/scoreodonto.com node --check backend/server.js docker compose build scoreodonto-backend scoreodonto-frontend docker compose up -d --force-recreate scoreodonto-backend scoreodonto-frontend # conferir em https://dev.scoreodonto.com # 2) ⚠️ transitório (até Passo 5): a TAG precisa casar com frontend/constants.ts node update-version.js # bump (ex. V1.0.14 → V1.0.15) # 3) commit + push → CI builda e publica : + : + :latest (NÃO deploya) git add -A && git commit -m "feat: (vX.Y.Z)" git push origin main # 4) tag = "aprovado p/ produção" → CI (hoje REBUILDA ✅) promove + deploya na VPS1 git tag v1.0.15 && git push origin v1.0.15 # 5) verificar SEM SSH: curl -s https://scoreodonto.com/api/version # → version/commit/environment ``` **Backup do banco de PRODUÇÃO antes de releases sensíveis** (migrações rodam no boot; aditivas/idempotentes): ```bash ssh deploy@10.99.0.3 'pg_dump -Fc scoreodonto -f ~/backups/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 ✅ Reverter o **código** é seguro (migrações só adicionam). Por imagem, **sem rebuild**: ```bash ssh deploy@10.99.0.1 /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`). --- ## 7. Passo 5 — "tag promove, não builda" 🎯 Desenho **aprovado (17/jun/2026), não aplicado**. Refina o CI existente para Build Once → Promote → Deploy: - `build.yml` → 2 jobs por ref: **build** (só push; publica `:`+`:latest`, sem `APP_VERSION`); **promote** (só tag; re-tag `:→:vX.Y.Z` via `docker buildx imagetools create`, sem rebuild) → deploy. - **Validação de integridade antes do deploy:** existe `backend:` e `frontend:`? digests válidos? alias `:vX.Y.Z` == digest de `:`? front e back do **mesmo commit**? Só então deploy (evita deploy parcial). - Decisões fechadas: manter **`:`** (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.