0030 — Skill ship: deploy do main em produção¶
| Status | CONCLUÍDA |
| Branch | spec/0030-skill-ship-deploy |
| Criada em | 2026-08-08 |
| Apps afetados | ferramental (.claude/skills/ship/) · api/web/e2e sem impacto |
Status:
RASCUNHO→APROVADA→EM IMPLEMENTAÇÃO→CONCLUÍDAEsta é uma spec de ferramental (uma skill do Claude Code), não de feature. Não cria rota, modelo nem tela — os apps entram como "sem impacto". É o comando futuro de deploy de rotina que a spec 0028 deixou explicitamente de fora (§3 daquela spec).
1. Problema¶
A stack de produção (spec 0028) está no ar em https://app.garantora.com.br/ (box srv01 = 179.198.118.166, repo em /opt/garantora/infra/prd). Mas não há um comando de deploy de rotina: subir uma versão nova do código hoje é fazer à mão, na box, a sequência git pull → make up → (backup → migrate) → verificar — fácil de errar a ordem, esquecer o backup antes de uma migration, ou aplicar sem olhar o que está indo para o ar. A spec 0028 deixou o Makefile sem alvo deploy de propósito, para que o desenho do deploy fosse tratado por completo aqui.
2. Objetivo¶
Uma skill ship que, chamada da máquina do desenvolvedor, faz o deploy do que está em origin/main para a produção com segurança: mostra o que vai mudar (commits e migrations), pede confirmação, e então atualiza o código na box, rebuilda, faz backup antes de migrar (só quando há migration pendente) e verifica que subiu de verdade.
3. Fora de escopo¶
- Publicar o código (
git push). Oshipnão faz push (respeita a regra doCLAUDE.md). Ele deploya o que já está emorigin/maine recusa se omainlocal estiver à frente — você dá o push primeiro. Separa "publicar" de "implantar". - Rollback automatizado. O
shipnão reverte sozinho. Em falha, ele para e relata, e o README/skill documenta o rollback manual (redeploy do commit anterior +make restoredo dump gerado antes da migration). Automatizar rollback fica para outra spec, se preciso. - Seed.
make seedé one-time do provisionamento (já feito). Oshipnunca semeia. - Provisionar a box. Instalar Docker/make, DNS,
.env, firewall — é a spec 0028. Oshippressupõe a box já provisionada e a stack já existente. - Outros ambientes.
shipé só produção (srv01). Dev-remote tem o seu próprio fluxo (/devremote-rebuild). - CI/CD. Continua sem pipeline;
shipé acionado por pessoa, com gate de confirmação.
4. Regras de negócio (comportamento da skill)¶
Numeradas para citação nos critérios de aceite.
- RN1. Alvo fixo de produção. O
shipopera sobresrv01(root@179.198.118.166), repo em/opt/garantora, stack eminfra/prd, domínioapp.garantora.com.br. Constantes na skill (um ponto só para mudar se a box mudar). - RN2. Deploya
origin/main, nunca o local. Antes de tudo,git fetch origine compara: se omainlocal estiver à frente deorigin/main(commits não publicados), oshiprecusa e instrui a dargit pushprimeiro. Não faz push por conta própria (§3). O que vai para produção é exatamenteorigin/main. - RN3. Pré-visualização + confirmação (gate humano). Antes de tocar a box, o
shipmostra: (a) os commits que entram —git log <deployado>..origin/main --oneline, onde<deployado>é oHEADatual de/opt/garantorana box; (b) se há migration pendente (RN4); (c) se haverá backup (RN5). Só prossegue com confirmação explícita do usuário. Sem confirmação, nada muda na produção. - RN4. Detecção de migration pendente (sem rebuildar antes). O
shipdetecta migration olhando o diff:git diff --name-only <deployado>..origin/main -- apps/api/alembic/versions/. Se surgirem arquivos novos ali, há migration pendente; senão, não há. É sinal pré-voo, não exige o código novo já buildado. - RN5. Backup antes de migrar (só quando há migration). Havendo migration pendente (RN4), o
shiprodamake backupantes domake migrate, garantindo um dump imediatamente anterior à mudança de schema. Sem migration pendente, pula backup e migrate. - RN6. Sequência de deploy. Na ordem, após a confirmação: (1)
git pull --ff-onlyem/opt/garantora(com agent forwarding para o repo privado — RN8); (2)make up(rebuild + recreate das imagens api/web); (3) se migration pendente:make backup→make migrate. Nunca migra antes de rebuildar; nunca migra sem backup. - RN7. Verificação pós-deploy. Ao fim, o
shipconfere e relata:HEADde/opt/garantora==origin/main;https://app.garantora.com.br/api/v1/healthresponde{"status":"ok","db":"ok"};https://app.garantora.com.br/responde 200 com TLS válido. Qualquer falha aqui é reportada como falha do deploy (não "deu certo"). - RN8. Acesso SSH sem deixar rastro. O
pullna box precisa de auth no GitHub via agent forwarding (ssh -A). Se a chave (default~/.ssh/id_ed25519, configurável) não estiver nossh-agent, oshipa adiciona para a operação e a remove ao final, restaurando o estado anterior do agent — não deixa a chave carregada. - RN9. Falhou, para. Qualquer passo que falhe (recusa da RN2, pull não-ff, build quebrado, migration com erro, healthcheck vermelho) interrompe o
shipcom mensagem clara do que falhou e do estado em que a produção ficou. Não continua "no escuro". - RN10. Idempotente quando não há nada novo. Se
origin/main==HEADdeployado, oshipdiz "produção já está noorigin/main" e encerra sem rebuildar.
5. Impacto por app¶
.claude/skills/ship/ (o trabalho desta spec)¶
SKILL.md— frontmatter (name: ship,description,when_to_use,argument-hint)- o fluxo: pré-voo (RN2, RN4), pré-visualização e confirmação (RN3), deploy (RN6), verificação (RN7). O gate de confirmação é conduzido pela skill (Claude mostra o diff e pede OK), não por um script mudo.
scripts/(conforme a implementação decidir):- um script de pré-voo que coleta, da box, o
HEADdeployado e devolve o diff de commits + se há migration pendente, para a skill apresentar; - um script de deploy que executa a sequência da RN6 na box (pull → up → [backup → migrate]) e o healthcheck da RN7. Constantes (host, path, domínio, chave) no topo.
- Sem alvo
deployno Makefile deinfra/prd— a orquestração (fetch/compare, gate, agent forwarding, verificação) é da skill; o Makefile segue só com os tijolos (up,migrate,backup, …). Alternativa aceitável na implementação: adicionar um alvomake deploysimples que o script da skill chame, se ficar mais limpo — mas o gate e o agent forwarding continuam na skill.
apps/api · apps/web · apps/e2e¶
Sem impacto. A skill não altera código de aplicação; só orquestra o deploy do que já está em origin/main.
6. Contrato de API¶
Sem impacto. Nenhuma rota nova ou alterada. A skill apenas consome /api/v1/health na verificação pós-deploy.
7. Critérios de aceite¶
Verificáveis por quem não escreveu a skill.
Estado: exercitado num
/shipreal em 2026-08-08 (deploy de536457a → 5b67a0c, sem migration). O caminho principal (pré-voo → gate → deploy → verificação) está provado. CA1/CA4/CA6 têm lógica pronta mas não foram disparados (exigiriam um commit local não publicado, uma migration nova, e uma falha real, respectivamente) — ver Registro.
- [ ] CA1. Com
mainlocal à frente deorigin/main, oshiprecusa o deploy e instrui a dargit push— nada muda na box. (RN2) — lógica pronta (rev-list --count), não exercitada (exigiria commit local não publicado). - [x] CA2. Com
origin/mainà frente do que está deployado, oshipmostra os commits que entram e se há migration pendente. Verificado: o pré-voo real listou os 2 commits pendentes e detectou "sem migration" (MIGRATE=0). O "só prossegue após confirmação" é o gate da SKILL (Passo 2). (RN3, RN4) - [x] CA3. Deploy sem migration: o
shipfezpull → make up, pulou backup e migrate (log "sem migration pendente — backup/migrate pulados") e a verificação passou. Verificado no deploy real. (RN5, RN6, RN7) - [ ] CA4. Num deploy com migration nova, o
shiprodamake backupantes demake migratee um dump novo existe. (RN5, RN6) — lógica pronta; não exercitada (o deploy real não carregava migration). Fica para o 1º deploy com mudança de schema. - [x] CA5. Fim do deploy:
HEADde/opt/garantora==origin/main(5b67a0c),/api/v1/health={"db":"ok"}e/= HTTP 200 TLS válido. Verificado. (RN7) - [ ] CA6. Se um passo falha (ex.: build quebrado), o
shippara e relata o passo e o estado; não segue como se tivesse dado certo. (RN9) —set -enos scripts garante a parada; não exercitado com falha real. - [x] CA7. A chave não estava no
ssh-agent; oshipa carregou e removeu ao final (log "chave removida do ssh-agent (estado restaurado)"). Verificado. (RN8) - [x] CA8. Com
origin/main== HEAD deployado, oshipencerra dizendo que produção já está atualizada, sem rebuildar. (RN10) — o pré-voo tem o exit 4 para isso; a lógica (DEPLOYED == ORIGIN) foi confirmada por leitura no run real (hoje há 2 commits à frente, então caiu no exit 0, correto).
8. Plano de implementação¶
A ordem api → web → e2e do template não se aplica (é ferramental). Ordem real:
- [x]
.claude/skills/ship/SKILL.md— frontmatter + fluxo (pré-voo → gate → deploy → verificação) - [x]
.claude/skills/ship/scripts/—config.sh(constantes, override porSHIP_*),preflight.sh(fetch/compare + diff + migration pendente) edeploy.sh(pull → up → [backup → migrate], com gestão do ssh-agent). Healthcheck ficou na SKILL (curl externo). - [x] Validação estática + pré-voo real:
bash -nnos três scripts OK; pré-voo rodado de verdade (read-only) contra a produção — listou os commits pendentes eMIGRATE=0. O caminho de deploy não foi exercitado (é ação do usuário, com gate). - [x]
make test/make lint— não afetados (nenhum código de app tocado) - [x]
READMEdeinfra/prdatualizado: aponta o/shipcomo o deploy de rotina e o rollback manual (antes dizia "comando futuro").
make test/make linte o browser do template não se aplicam a ferramental; a validação é o ensaio da skill num deploy real, combinado com o usuário.
9. Decisões em aberto — resolvidas¶
- [x] DA1 — Migrations no deploy. ✅ Só quando há pendente (detecção por diff dos arquivos em
alembic/versions/, RN4); aplica após confirmação e backup. - [x] DA2 — Backup antes de migrar. ✅ Sim, e só quando há migration (RN5).
- [x] DA3 — Origem do deploy. ✅ Deploya
origin/main; recusa se o local estiver à frente (você dá push antes). Oshipnão faz push (RN2, §3). - [x] DA4 — Confirmação. ✅ Gate humano com diff de commits + migrations pendentes antes de tocar produção (RN3).
- [x] DA5 — Chave SSH. ✅ Agent forwarding; a skill adiciona a chave se faltar e remove ao final, restaurando o agent (RN8). Caminho da chave configurável (default
~/.ssh/id_ed25519). - [x] DA6 — Rollback. ✅ Fora de escopo automatizar (§3); documentar o manual (redeploy do commit anterior +
make restoredo dump).
10. Registro¶
Implementado em 2026-08-08. Skill ship criada em .claude/skills/ship/:
SKILL.mdconduz o fluxo (pré-voo → apresentar+confirmar → deploy → verificar) e documenta o rollback manual. O gate humano (RN3) é da skill: mostra o relatório do pré-voo e espera "sim" antes do deploy.scripts/config.sh— constantes (host179.198.118.166,/opt/garantora, domínioapp.garantora.com.br, chave), cada uma com overrideSHIP_*.scripts/preflight.sh— read-only:git fetch, recusa se o local está à frente (exit 3), "nada a fazer" se igual (exit 4), senão lista os commits e detecta migration pelo diff emalembic/versions/(imprimeMIGRATE=0/1, exit 0).scripts/deploy.sh— pós-confirmação: gerencia o ssh-agent (adiciona a chave se faltar, remove notrap EXIT), e numa sessãossh -Afazpull → make up → [backup → migrate se MIGRATE=1].
Desvios/decisões da implementação:
- Healthcheck na SKILL, não no
deploy.sh. A verificação (RN7) écurlao domínio público — melhor rodar de fora (da máquina do dev), pelo caminho real, do que de dentro da box. Ficou no Passo 4 da SKILL. - Sem alvo
make deploy. Como previa a spec (§5), a orquestração ficou toda na skill; o Makefile deinfra/prdsegue só com os tijolos (up,backup,migrate, …). - Constantes no
config.sh(sourced pelos dois scripts) em vez de repetidas — um ponto só de verdade, com overridesSHIP_*para teste.
Validação: bash -n nos três scripts OK; o pré-voo foi exercitado contra a produção real (read-only) e comportou-se certo — hoje a box está em 536457a e origin/main em 5b67a0c, então listou os 2 commits pendentes (o chore/0028) e MIGRATE=0 (sem migration). O deploy em si não foi rodado — é ação de produção, com o gate de confirmação da skill; fica para um /ship real do usuário, que fecha CA3/CA5/CA6/CA7.
e2e: sem impacto — ferramental não tem spec Playwright.
Primeiro /ship real (2026-08-08). Rodado de ponta a ponta: pré-voo (exit 0, MIGRATE=0, 2 commits) → gate (usuário confirmou) → deploy.sh (background) → verificação. Resultado: git pull 536457a → 5b67a0c, make up (imagens rebuildadas, cache hit pois só Makefile/spec mudaram; containers recriados e healthy), backup/migrate pulados (sem migration), HEAD na box == origin/main (5b67a0c), chave adicionada e removida do ssh-agent no fim. Verificação externa: /api/v1/health = {"db":"ok"}, / = HTTP 200 TLS 0. Fechou CA2, CA3, CA5, CA7, CA8. Restam CA1 (recusa), CA4 (deploy com migration) e CA6 (falha) — lógica pronta, disparam quando a condição ocorrer. Status → CONCLUÍDA.
Nota: o deploy levou à produção os commits do chore/0028 (que estavam em origin/main); a própria skill ship ainda não está em origin/main (é este trabalho, não commitado) — ela rodou da árvore local. Entra em produção no próximo ship depois de mesclada.