Implantação — integrador no Coolify (um recurso por cliente)¶
Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-09-15
Como uma instância do integrador fica de pé no Coolify, como se troca o jar pelo native, como se confere que subiu e como se desliga. O integrador é multi-tenant por implantação: cada cliente tem o seu recurso Coolify, o seu subdomínio e o seu banco — não há um servidor único servindo vários.
O desenho — componentes, fluxo de dados, modelo de dados, autenticação — é do livro: Documentação Completa. A lista completa de propriedades e ENV é da configuração. Por que native e jar convivem e a ordem da troca estão na decisão 0028. Aqui fica só o que é de operar.
Quando usar¶
- Ao provisionar um cliente novo.
- Ao trocar o jar pelo native numa instância (§ Troca jar → native).
- Ao atualizar a instância de um cliente, ou ao reverter uma versão.
- Ao precisar desligar o webhook de saída sem derrubar o app (§ Reversão).
- Ao instalar um integrador-client no cliente (§ Ao instalar um integrador-client).
Implantação de uma integração inteira (com int-pied, PowerSync e clientes) tem runbook próprio: Maxsul (PIED).
Pré-requisitos¶
- Postgres do cliente provisionado, com um usuário que tenha DDL no schema (o Flyway roda no start do app).
- DNS do subdomínio do cliente (
int.<cli>.xadm.biz) apontando para o Coolify; na troca, também o do jar (int-jar.<cli>.xadm.biz). - Segredos do § Variáveis à mão (cofre da X-Adm).
- Imagem publicada: o
pipeline.ymlbuilda e publicafonte.xadm.biz/xadm/integrador-servercom as tags móveisnative-amd64ejar-amd64; o Coolify puxa, não builda. A primeira imagem vem de uma release ou de umworkflow_dispatch.
Topologia de operação¶
%% caption: O que se implanta, por cliente (durante a troca, os dois recursos no ar)
flowchart TB
PROXY["Proxy do Coolify<br/>(TLS automático)"]
NAT["integrador-server-<cli>-native-pull<br/>imagem native-amd64<br/>porta interna 8080"]
JAR["integrador-server-<cli>-jar-pull<br/>imagem jar-amd64<br/>porta interna 8080"]
DB[("Postgres do cliente<br/>(db_<cliente>)")]
PROXY -->|"int.<cli>.xadm.biz"| NAT
PROXY -->|"int.<cli>.xadm.biz até provar o native,<br/>depois int-jar.<cli>.xadm.biz"| JAR
NAT --> DB
JAR --> DB
| Peça | Onde | Nota |
|---|---|---|
| Integrador native | recurso integrador-server-<cli>-native-pull, Build Pack Docker Image |
o alvo padrão; porta interna 8080 |
| Integrador jar | recurso integrador-server-<cli>-jar-pull, Build Pack Docker Image |
fica no ar ao lado do native até a sexta instância trocar, no int.<cli> até a instância provar o native e no int-jar.<cli> depois; em seguida, parado como fallback |
| Proxy | Traefik do Coolify | termina TLS; o contrato com apps e proxies é o host (DNS), não a porta |
| Postgres | banco do cliente | o app é dono do schema e roda o Flyway no start |
Subdomínio: um por cliente, int.<cli>.xadm.biz (ex.: a instância da Maxsul é
int.maxsul.xadm.biz). O FQDN de cada cliente já implantado é o que está no recurso Coolify —
confira lá, não presuma pelo padrão.
Como o deploy acha o recurso: o reconcile do control-plane do central tira a instância do
primeiro FQDN do recurso, na forma int.<cli>.xadm.biz e, no jar da troca, int-jar.<cli>.xadm.biz
(Coolify da casa). O native
leva o int.<cli> como primeiro domínio. O jar leva o int.<cli> até a instância provar o native e o
int-jar.<cli> depois — só quando o reconcile no ar aceita esse host: o INSTANCE_FROM_FQDN do
DeployReconcileService do central-backend casa int-jar. Um recurso com domínio que o reconcile não
lê fica sem instância e colide no mapa de deploy com os de outros clientes.
Variáveis de ambiente¶
A lista completa, com defaults e o mapeamento propriedade Micronaut ↔ ENV, é da configuração — não se copia aqui. O mínimo para subir:
| Variável | Nota |
|---|---|
CLIENTE |
identificador do tenant; vira a tag cliente de todo evento no GlitchTip |
DATASOURCES_DEFAULT_URL |
JDBC do banco do cliente |
DATASOURCES_DEFAULT_USERNAME / DATASOURCES_DEFAULT_PASSWORD |
segredo — usuário com DDL no schema |
INTEGRADOR_API_TOKEN |
segredo, obrigatório — Bearer estático de /api/**. Nome por destino; o antigo API_BEARER_TOKEN não é lido (sem fallback). Ausente ou vazia, o app não sobe: o BearerTokenGuard da xadm-seguranca recusa o boot — ver segurança |
SENTRY_DSN |
obrigatório — DSN do GlitchTip (client-grade, fonte: docs/app.json). O nome GLITCHTIP_DSN não é lido: sem SENTRY_DSN o Sentry não inicializa e erro nenhum é reportado, só um WARN no boot (observabilidade) |
SENTRY_ENVIRONMENT |
staging ou production |
Sem MICRONAUT_ENVIRONMENTS: produção = application.yml + ENV. Opcionais por cliente: PUSH_*
(FCM), AUTH_* (login Google — segurança), WEBSTORM_* (poke de
saída ao tradutor, só onde há tradutor) e CENTRAL_API_TOKEN + CLIENTE_ID (heartbeat, só onde há
integrador-client — § Ao instalar um integrador-client). Segredos vão em secret do Coolify, nunca no
repo. Os recursos native e jar de uma instância têm as mesmas variáveis.
Pares que têm de casar¶
| Ponta A | Ponta B | Sintoma se divergirem |
|---|---|---|
INTEGRADOR_API_TOKEN (aqui; sem fallback — o antigo API_BEARER_TOKEN é ignorado) |
INTEGRADOR_API_TOKEN do tradutor (adapter CSV) / Bearer do cliente X-Adm / int-pied / uploadToken dos clientes PowerSync |
401 no PUT /api/v1/xadm ou no write-back; nada entra e o remetente acumula erro |
WEBSTORM_API_TOKEN (aqui; sem fallback — ${WEBSTORM_API_TOKEN:} também não é aninhado) |
WEBSTORM_API_TOKEN (tradutor) |
o poke responde 401; o sweep do tradutor mascara — envia atrasado e ninguém nota |
CENTRAL_API_TOKEN (aqui) |
CENTRAL_API_TOKEN dos recursos do central-backend |
o heartbeat responde 502 heartbeat_recusado + ERROR (invalid_token); a instalação some do painel e, depois de 6h úteis, o central alerta silêncio |
CLIENTE_ID (aqui) |
clientes.cliente_id no central-backend |
422 cliente_desconhecido no central, 502 heartbeat_recusado + ERROR aqui; a instalação nunca é registrada |
| variáveis do recurso native | variáveis do recurso jar da mesma instância | cada cópia se comporta diferente conforme a requisição cai numa ou noutra |
Passos — cliente novo¶
- Banco. Garantir o Postgres do cliente acessível e o usuário com DDL no schema.
- Recurso.
+ New Resource→ Build PackDocker Image→fonte.xadm.biz/xadm/integrador-server:native-amd64, nomeintegrador-server-<cli>-native-pull. Auto-deploy desligado: o deploy é o jobdeploydopipeline.yml, pelo control-plane. - Porta e domínio. Porta interna
8080; FQDNhttps://int.<cli>.xadm.bizcomo primeiro domínio. - Health check.
GET /health(oHEALTHCHECKda imagem já bate no mesmo endpoint). - Limite e reserva de memória do recurso, pela baseline de operação do Coolify.
- Variáveis. Preencher o § Variáveis.
- Deploy.
Redeployno recurso (ou a próxima release); o Flyway roda no start — acompanhar o log da primeira subida.
Troca jar → native (instância que roda jar)¶
A ordem entre as instâncias está na decisão 0028: a primeira é uma instância com o push ligado, e cada seguinte só depois de smoke verde e GlitchTip sem exceção nova na anterior.
- Crie o
integrador-server-<cli>-native-pullcomo nos passos 2–6 acima, com as mesmas variáveis do-jar-pullda instância e o mesmo FQDN (int.<cli>.xadm.biz) como primeiro domínio. Não mexa no recurso jar. - Deploy do native:
Redeployno recurso novo. O proxy passa a dividir o tráfego da instância entre jar e native. - Confira pelo § Verificação, e o split pelo
flavor: 20 chamadas ao/healthtêm de mostrar"flavor":"native"e"flavor":"jvm". - Mova o jar para o
int-jar.<cli>.xadm.biz, com smoke verde e GlitchTip sem exceção nova na instância. Antes, confira as duas pré-condições: o reconcile no ar aceita o host (§ Topologia, "Como o deploy acha o recurso") e o DNS deint-jar.<cli>.xadm.bizresolve para o Coolify. Sem o reconcile, o jar segue noint.<cli>e este passo espera. No-jar-pull, troque o domínio porhttps://int-jar.<cli>.xadm.bize façaRedeploy. O native fica sozinho noint.<cli>: 20 chamadas ao/healthsó mostram"flavor":"native", e ohttps://int-jar.<cli>.xadm.biz/healthmostra"flavor":"jvm". Rode o reconcile e confira no mapa de deploy o-jar-pullcom a instância<cli>e o alvojar. - Deixe os dois no ar. O jar desta instância não para agora: enquanto
jarestiver nobuild.targets, cada release redeploya o-jar-pull, e um recurso parado voltaria a subir. - Depois da sexta instância: a release que tira
jardobuild.targets; em seguida, pare os seis-jar-pull(ficam parados como fallback, nunca apagados).
Verificação¶
Resultado esperado conferido no código, não no que parece razoável.
GET /health→{"status":"UP","versao":"X.Y.Z","flavor":"native",…}— shape do/healthdaxadm-comum-web. O{"status":"ok"}é do/api/health, outro endpoint: esperar"ok"no/healthdá falso alarme no dia do deploy. Aversaotem de bater com a tag implantada (health e versão).- Abrir a raiz (
/) → as telas sobem com o cabeçalho X-Adm e o nome do cliente (UI das telas). PUT /api/v1/xadmsem o Bearer →401. Com o Bearer →200.- Provocar um erro controlado e conferir o evento no GlitchTip filtrando por
cliente=<CLIENTE>. - Log do start sem falha de Flyway (migration pendente aplicada).
- Na instância piloto da troca: uma nota que dispara push chega uma vez ao celular, e aparece uma
linha por mensagem em
/push-enviadas.
Ao instalar um integrador-client¶
O heartbeat do integrador-client passa por esta instância a caminho do central-backend
(contrato). Nas instâncias sem client as duas envs ficam vazias:
nenhum heartbeat chega, e a config vazia só responde 503 se alguém chamar a rota.
- Ordem. A versão do integrador-server com
POST /api/v1/heartbeattem de estar no ar antes do jar do client com heartbeat. Ao contrário, o client leva404e só avisa, mas aqui cada404autenticado vira ERROR no GlitchTip — uma issue por hora. - Central. Conferir que o cliente existe na tabela
clientesdo central-backend (ocliente_id, ex.:maxsul). - Envs.
CLIENTE_ID= essecliente_id;CENTRAL_API_TOKEN= o mesmo valor cadastrado nos recursos do central — nos recursos native e jar da instância. Redeploy. - Prova, antes do client.
curl -X POST https://<fqdn>/api/v1/heartbeat -H "Authorization: Bearer <INTEGRADOR_API_TOKEN>" -H "Content-Type: application/json" -d '{"versao":"smoke","fluxos":[]}'→204. A instalação aparece no painel do central com a versãosmokeaté o client real bater (no máximo 1h depois da instalação do jar).502/503aqui: ver incidentes comuns.
Reversão¶
| Caminho | Efeito |
|---|---|
Parar o -native-pull da instância (durante a troca) |
Com o jar ainda no int.<cli>, ele segue atendendo sozinho. Com o jar já no int-jar.<cli>, devolva antes o int.<cli> como primeiro domínio do -jar-pull e faça Redeploy: o recurso que fica assume o domínio antes de o outro parar, senão o int.<cli> responde 503. É a volta do native |
| Parar os recursos da instância (Coolify) | Para tudo nesta instância. Mais simples e garantido |
Apontar a tag imutável <sha>-native (ou <sha>-jar) no recurso e Redeploy |
Volta a versão. As migrations são aditivas — reverter o container não desfaz schema; migration nova em produção só se reverte à mão |
WEBSTORM_WEBHOOK_ENABLED=false |
Corta só o poke de saída ao tradutor (EstoqueWebhookListener). ⚠️ Não para a integração: o tradutor tem sweep próprio e segue descobrindo mudança no banco sozinho — desligar o envio ao parceiro é no repo do tradutor |
PUSH_ENABLED=false |
Corta só o push FCM. Não afeta ingestão nem replicação |
Esvaziar CLIENTE_ID ou CENTRAL_API_TOKEN |
⚠️ Não é um desligamento limpo — não há flag do heartbeat neste server: cada heartbeat passa a responder 503 heartbeat_desligado com ERROR no GlitchTip. Desligar o heartbeat é no client (heartbeat.intervaloMinutos=0); parar de acompanhar a instalação é no painel do central |
Nenhuma dessas flags corta a entrada: enquanto o app está no ar, o X-Adm e o int-pied seguem
podendo PUT /api/v1/xadm com o Bearer válido. Para fechar a entrada, pare o container ou
rotacione o INTEGRADOR_API_TOKEN.
Referências¶
- Configuração completa (propriedades ↔ ENV): application-config.md
- Build e diagnóstico do native: native-image.md
- Perfis Micronaut: micronaut-profiles.md
- Observabilidade (tag
cliente): observabilidade.md - Implantação de integração completa: Maxsul (PIED)
- Operação no Coolify da casa: docs.xadm.biz/infraestrutura/coolify