Pular para conteúdo

Deploy — control-plane no central-backend

Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-09-12

Aplica-se a: perfis app e config com recurso no Coolify.

Como o CI dispara o deploy depois do build off-host. A norma está em Entrega e o porquê na 0026; esta página é o contrato operável.

Modos de deploy (por tipo de projeto)

Tipo de projeto Recurso Coolify Quem builda Gatilho
Server Micronaut native-pull (padrão); jar-pull quando jar é declarado com motivo job build_native/build_jar do pipeline.yml control-plane, target=native/jar
Flutter web web-pull job build_web control-plane, target=web
Stack compose (perfil config) compose do git o Coolify, a partir do git control-plane, target=compose
Site de docs do central build do Dockerfile o Coolify, a partir do git auto-deploy no push
sulplata-ho-scraper (Node) build do Dockerfile o Coolify, a partir do git auto-deploy no push
vantroba-powersync compose do git o Coolify, a partir do git auto-deploy no push (e control-plane)

As três últimas linhas são os recursos que ainda seguem em auto-deploy: dívida do central, com a migração para o control-plane no backlog. Todo o resto dispara pelo control-plane, com o gate verde.

O vantroba-powersync é o caso a corrigir primeiro: é a única das seis stacks PowerSync com auto-deploy ligado, e o gatilho do Coolify não conhece o filtro de caminho do pipeline-config.yml — qualquer push no repo, inclusive de doc, reinicia a stack.

O fluxo

flowchart TB
    CI["job deploy do pipeline.yml"] -- "POST /api/ci/deploy<br/>Bearer CENTRAL_DEPLOY_TOKEN" --> C[central-backend]
    C -- "resolve o recurso<br/>app_deploy_targets" --> C
    C -- "POST /deploy?uuid=" --> K[API do Coolify]
    K -- "rolling: novo sobe, health, velho drena" --> A[Container do app]

O contrato

POST https://central-backend.xadm.biz/api/ci/deploy
Authorization: Bearer <CENTRAL_DEPLOY_TOKEN>
Content-Type: application/json

# recurso que puxa imagem (jar/native/web)
{ "image": "fonte.xadm.biz/xadm/<slug>:<target>-amd64", "target": "<target>" }

# recurso compose que o Coolify builda do git
{ "app_id": "<slug do repo git>", "target": "compose" }

→ 200 { "deployments": [ { "coolify_uuid": "8wih…", "deployment_uuid": "aavd…", "status": "queued" } ] }
  • Auth: token dedicado CENTRAL_DEPLOY_TOKEN, validado por um filtro próprio em /api/ci/**.
  • Âncora: com image, o slug da imagem; sem image, o app_id é o slug e o target é obrigatório (400 missing_anchor / 400 missing_target).
  • target: jar | native | web | compose.
  • image é rastro: o recurso tem tag fixa (<target>-amd64) e o deploy re-puxa essa tag; o central não troca a imagem do recurso.
  • Resposta assíncrona: queued assim que o Coolify enfileira; o Coolify faz o rolling. Quem verifica produção é o smoke.
  • Endpoint do Coolify: POST {coolify}/deploy?uuid={uuid}&force=false — não o /restart, que só reinicia a mesma imagem.
  • URL única, flavor-agnóstica: o CENTRAL_DEPLOY_URL aponta central-backend.xadm.biz, nunca o domínio de um flavor de build ou de uma instância. O aviso de publish das docs deriva dele: CENTRAL_DOCS_URL="${CENTRAL_DEPLOY_URL%/deploy}/docs-published".
  • Rollback automático: pendente no control-plane. O endpoint aceita só a tag móvel (…:<target>-amd64); a tag imutável <sha>-<target> volta 400 invalid_image. Até o control-plane aceitar a imutável e trocar a tag do recurso antes do deploy, o smoke reprova e avisa, e o operador reverte apontando <sha>-<target> no recurso e fazendo Redeploy.

O mapa — app_deploy_targets

O mapa vive no banco do central-backend, sem chave estrangeira para o catálogo de apps. Desde a 0038 o app_id e o slug da imagem são o mesmo valor, mas a FK continua fora por outro motivo: nem todo recurso deployável tem linha em apps — as stacks compose (PowerSync, uma por cliente) são ancoradas pelo slug do repo git e não são apps do catálogo. Uma FK aqui impediria de mapeá-las:

coluna
registry_slug slug do artefato que o recurso consome — a chave do reconcile: o slug da imagem (0024) ou, no compose, o slug do repo git
target jar | native | web | compose
instance instância, derivada do FQDN int.<cli>.xadm.biz ou int-jar.<cli>.xadm.biz (o jar da troca); vazia no app de uma instância só
coolify_uuid uuid do recurso Coolify, gravado pelo reconcile
fqdn domínio do recurso

Unicidade por (registry_slug, target, coalesce(instance, '')). Cada disparo grava uma linha em deploy_audit (slug, alvo, instância, recurso, deployment, resultado e uma referência não reversível ao token).

Formatos de recurso, na fórmula <slug>-[<instance>-]<target>-pull:

  • um recurso — o Flutter web (<slug>-web-pull);
  • dois recursos no mesmo domínio — native e jar do mesmo app; o target desambigua (coolify);
  • multi-instância — o integrador-server, um recurso por cliente e alvo (integrador-server-<cli>-<target>-pull).

(registry_slug, target) sem instance seleciona todas as linhas que casam: um release do integrador deploya todas as instâncias; instance presente restringe a uma.

Reconcile — por que o mapa não envelhece

Recurso recriado (blue-green) ganha uuid novo, então a chave de casamento nunca é o uuid nem o nome do recurso. O reconcile lista os recursos do Coolify e casa:

  • recurso que puxa imagem pelo docker_registry_image_name (último segmento = registry_slug);
  • recurso compose (build_pack=dockercompose) pelo slug do repositório git (último segmento do git_repository, sem .git e sem credencial): …/onpetro-powersync.git → onpetro-powersync, target=compose. Recurso compose sem repositório git é pulado; build_pack fora de {dockerimage, dockercompose} é ignorado.

Renomear o recurso não exige ação. Recurso cujo slug não casa com nada é logado como órfão; o reconcile nunca apaga linha nem inventa app. Roda no boot, sob demanda e quando a resolução não acha o recurso. Assunção: um repo git = uma stack compose; a segunda stack do mesmo repo precisa de instance.

O central se deploya pelo próprio endpoint

O job deploy do pipeline.yml do central-backend chama o endpoint público do próprio central, que relaya à API do Coolify de dentro da casa. É seguro: o central atual está no ar no disparo, e o Coolify enfileira e só troca o container quando o novo fica saudável.

Central fora do ar: o endpoint não responde e o deploy é à mão, de dentro da casa — Redeploy do recurso na UI do Coolify (apontando a tag <sha>-<target> anterior, se a nova é a quebrada), ou POST /api/v1/deploy?uuid=<uuid> na API do Coolify.

Roteiro de adoção

  1. Recurso Coolify pull-only (ou compose do git) com auto-deploy desligado.
  2. No repo, no GitHub: secret CENTRAL_DEPLOY_TOKEN e variável CENTRAL_DEPLOY_URL (https://central-backend.xadm.biz/api/ci/deploy).
  3. Reconcile no central (sob demanda) e um deploy de teste.
  4. Apagar do GitHub DEPLOY_SSH_KEY, COOLIFY_TOKEN e COOLIFY_NATIVE_UUID e revogar esse token no Coolify — o gatilho por SSH não existe mais.