0026 — Control-plane de deploy no central-backend¶
Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-09-12 · Decidido em: 2026-08-30
Contexto¶
O build off-host (0022) dispara o deploy por uma ponte SSH
na VM de produção: o build-deploy.yml faz ssh …:65024 "deploy <uuid>" e a ponte — uma chave
forced-command com allow-list de uuids — chama a API local do Coolify. A ponte existe porque a
API do Coolify só aceita o IP da casa, e o runner do GitHub tem IP arbitrário.
O allow-list fica órfão em todo cutover blue-green: migrar um recurso cria um recurso Coolify novo
(uuid novo) e deleta o velho → o uuid autorizado some e o novo nunca entra. Verificado em 2026-08-30: a
ponte recusa os 19 recursos pull da frota inteira (refused: uuid nao autorizado), Java e Flutter.
Nunca quebrou só porque todo deploy até hoje foi via API direta (do IP da casa), nunca pela ponte — que
só seria exercida numa release-por-tag autônoma. A causa-raiz é dupla: o allow-list guarda uuid (o que
muda no cutover) como chave, e é mantido à mão num script fora de qualquer catálogo. E o central-backend
— que já é o catálogo de apps e já fala com a API do Coolify (CoolifyClient) — não participa do deploy.
Decisão¶
O deploy da casa é orquestrado pelo central-backend (control-plane). O CI dispara por API M2M; a ponte SSH, o forced-command e o allow-list são removidos.
- Contrato. O
build-deploy.ymlfazPOST /api/ci/deployno central com{app_id, target, image?}, autenticado por token dedicadoCENTRAL_DEPLOY_TOKEN(validado por um filtro próprio em/api/ci/**, no molde doSetupAuthFilter— não oAdminAuthFilter, que é tudo-ou-nada e super-privilegiaria o token). O central resolve(app_id, target[, instance])→ recurso(s) Coolify e dispara o deploy de cada um pela API do Coolify. Resposta assíncrona (queued+deployment_uuid); o Coolify faz o rolling.
Correção 2026-09-12: quem chama o
/api/ci/deployé o jobdeploydopipeline.yml(0027); obuild-deploy.ymlsaiu do kit. - Mapa auto-curável. Uma tabelaapp_deploy_targets(app_id, target, instance, registry_slug, coolify_uuid, fqdn) guarda o vínculo; um reconcile casa os recursos do Coolify pelo slug de imagem (registry_slug==docker_registry_image_namedo recurso ==slugdo app, 0024; detalhe na deploy.md), chave estável no cutover, nunca por uuid — nem pelo nome do recurso (o nome canônico<slug>-[<instance>-]<target>-pullé derivado/legível, não a chave; verificado empiricamente na Fase 2, 2026-09-03). Blue-green futuro cria uuid novo → o reconcile re-casa pelo slug de imagem. Fim da edição à mão.Correção 2026-09-05: a âncora do reconcile é o slug do artefato que o recurso consome — a imagem, ou o slug do repo git quando o Coolify builda do git (stack compose) —, e o
targetincluicompose. Contrato e reconcile em deploy. - Central se deploya pelo próprio/api/ci/deploy— auto-referência É o design. O jobdeploydopipeline.ymldo central-backend é byte-idêntico ao de qualquer app: bate no endpoint público do próprio central (BearerCENTRAL_DEPLOY_TOKEN), que relaya à API do Coolify localmente (o central roda dentro da casa → alcança o IP restrito). Seguro: o central atual está no ar no disparo, e a chamada ao Coolify enfileira e retorna antes do swap (rolling health-gated — se a nova falha, a anterior segue no ar). Break-glass (a única exceção): central fora do ar não se autodeploya → deploy manual pela UI/API do Coolify (de dentro da casa), no runbook (infraestrutura — deploy).Correção 2026-08-31: o central se deploya pelo próprio
/api/ci/deploy. O desenho original ("direto na API do Coolify, sem auto-referência") é inexequível do runner GitHub, cujo IP a API do Coolify não aceita (0027). - Na 0022: a topologia "dispara o deploy via ponte SSH" é substituída por este webhook no central.
O detalhe operável — corpo do endpoint, esquema da tabela, os 3 formatos de recurso, a rotina de reconcile, a migração — vive na página infraestrutura — deploy; a norma durável, na §9 da constituição (§1.9, um dono por fato).
Alternativas descartadas¶
- Manter a ponte + auto-whitelist no cutover. Faria cada blue-green adicionar o uuid novo ao allow-list e remover o velho. Resolve o sintoma, mas mantém dois segredos (chave SSH na VM + allow-list), um script fora de catálogo, e não dá visibilidade do que está deployado. Preterido — trata o sintoma, não a raiz.
- Resolução por domínio pura, sem tabela. O central já resolve uuid por
fqdn == production_url(CoolifyClient.resolveAppUuid). Mas isso quebra no canary (2 recursos no mesmo domínio) e no integrador (6 recursos, N domínios), não modelatarget, e a UI (Fase 3) teria que bater no Coolify a cada render, sem estado próprio. Preterido — a dimensão de deploy precisa ser persistida. - Coolify auto-deploy no push da imagem. O Coolify redeployaria sozinho ao detectar imagem nova no
registry. Menos controle do momento e sem orquestração/auditoria centralizada. Preterido — o
central se deploya pelo próprio
/api/ci/deploy(ver Decisão); a única exceção é o break-glass (central fora do ar → deploy manual pela UI/API do Coolify).
Consequências¶
app_deploy_targets(migrationV10no central-backend). A tabelaapps(11 linhas, identidade) ganha uma dimensão de deploy (19 linhas — os apps expandidos pelos recursos: 1:1, canary 1:2, integrador 1:6). Semeada com os uuids atuais + mantida pelo reconcile.- Migração big-bang. Os ~18
build-deploy.ymltrocam o stepssh …porcurl POSTao central e o secretDEPLOY_SSH_KEYporCENTRAL_DEPLOY_TOKENde uma vez; ao fim, a ponte + forced-command + allow-list são removidos. Sem regressão: a ponte já está morta (allow-list órfão) e tudo deploya via API. - Escopo faseado. Esta ADR é a Fase 1 (documentação). A Fase 2 (central-backend:
V10, reconcile,CoolifyClient.deploy(), o endpoint/api/ci/deploy+ filtro, a migração da frota) e a Fase 3 (central-ui: dashboard apps × recursos × estado × tipo × uuid, reusando/api/admin/monitor) têm decisão/spec próprias nos seus repos. - Encaixe constitucional. M2M
<DESTINO>_API_TOKEN(seguranca); contrato para caller não-Java = OpenAPI publicado (0020); o control-plane vive no central (§7); a norma durável na §9. - Auditoria. Todo deploy disparado deixa rastro (quem/qual token, app, target, uuids, resultado) — ação privilegiada exige rastro (seguranca).