Deploy do central-backend no Coolify¶
Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-09-15
O que é¶
Sobe (ou atualiza) o serviço auth.xadm.biz no Coolify, com HTTPS via Traefik e
acesso ao db_auth na rede interna.
Quando usar¶
- Primeiro deploy do serviço num ambiente.
- Mudança de variável de ambiente (chaves, banco, Firebase).
- O deploy de código não precisa deste runbook: é automático pelo control-plane — ver Deploy de código abaixo.
Pré-requisitos¶
- Chaves geradas (Gerar as chaves e segredos).
db_authacessível na rede do projetointegracao-compartilhado.- Acesso ao painel do Coolify.
Passos¶
-
Criar o recurso (primeira vez): Coolify → projeto
integracao-compartilhado→ New Resource → Docker Image, apontando pra imagem native que o CI publica (fonte.xadm.biz/xadm/central-backend:native-amd64; o app é native-only — decisão 0030).- Port:
8080 - Network: a rede compartilhada onde o
db_authestá acessível - Domains:
auth.xadm.bizcom Force HTTPS (Traefik emite o certificado) — é o host do issuer/JWKS e não muda (decisão 0020) —, maiscentral-backend.xadm.biz, o domínio estável do serviço (é oCENTRAL_DEPLOY_URLque o CI chama).
Build Pack = Docker Image não é preferência, é requisito do control-plane
O reconcile do
app_deploy_targetssó ancora recurso combuild_pack=dockerimage, derivando oregistry_slugdo nome da imagem e otargetdo prefixo da tag (0021). Recurso buildado do git (Dockerfile/nixpacks) não entra no mapa → oPOST /api/ci/deployresponde404 no_deploy_targete o jobdeploydopipeline.ymlquebra. A única outra família ancorada ébuild_pack=dockercompose, pelo slug do git repo comtarget='compose'(0026) — não é o caso deste serviço. - Port:
-
Variáveis de ambiente (mínimo de produção):
MICRONAUT_ENVIRONMENTS=prod DATASOURCES_DEFAULT_URL=jdbc:postgresql://postgresql:5432/db_auth DB_USER=auth_service DB_PASSWORD=<senha-do-db_auth> AUTH_PRIVATE_KEY=<base64 da chave privada> AUTH_KEY_ID=auth-1 AUTH_ISSUER=https://auth.xadm.biz # Cai para ≤ 60 (ADR central 0037) só depois que os apps que renovam sessão tiverem o refresh; # antes disso, app sem refresh re-loga a cada TTL (decisão 0034): AUTH_TOKEN_EXPIRATION_MINUTES=1440 # Opcional — teto da sessão de app-user desde o último login com credencial (refresh, decisão 0034); # máximo 168 (7 dias, ADR central 0037) — valor acima é cortado: AUTH_SESSION_MAX_HOURS=168 # Opcional — habilita login Firebase (admin e client-login): AUTH_FIREBASE_PROJECT_ID=<projeto-firebase> AUTH_XADM_EMAIL_DOMAIN=xadm.com.br # Opcional — notificação de novo acesso pendente (sem a chave, o envio é no-op): RESEND_API_KEY=<chave-resend> # Shared secret do integrador p/ /api/admin/**: CENTRAL_INTEGRATOR_TOKEN=<segredo> # Do CI p/ /api/ci/** — OBRIGATÓRIO: ausente ou vazio, o container NÃO SOBE # (DeployTokenGuard, ativo só com MICRONAUT_ENVIRONMENTS=prod — acima; o rolling # deploy mantém o anterior no ar). Mesmo valor no secret do GitHub. CENTRAL_DEPLOY_TOKEN=<segredo> # Dos integrador-servers p/ /api/integrador/** (heartbeat do integrador-client) — # OBRIGATÓRIO pelo mesmo motivo: ausente ou vazio, o container NÃO SOBE # (IntegradorTokenGuard, só em prod). MESMO valor do CENTRAL_API_TOKEN dos # integrador-servers. Só abre # /api/integrador/** — não tem relação com o CENTRAL_INTEGRATOR_TOKEN acima. CENTRAL_API_TOKEN=<segredo>Heartbeat, alerta de silêncio e o lado do integrador-server: runbook do heartbeat.
CORS. Não precisa configurar nada para app da casa: a allowlist já cobre
https://*.xadm.biz(todobi.<cliente>.xadm.biz) além docentral/logine das portas de dev.CENTRAL_CORS_EXTRA_ORIGINS(CSV) existe só para origem fora do domínio da casa — staging local em porta própria, por exemplo. Vazio em produção.E-mail. Produção usa Resend (
RESEND_API_KEY, remetente emRESEND_REMETENTE). O endpoint é property (RESEND_ENDPOINT, default a API da Resend) e existe para o teste apontar o transporte a um mock — não mexer em produção. O transporte SMTP (CENTRAL_EMAIL_SMTP_HOST,CENTRAL_EMAIL_SMTP_PORT,CENTRAL_EMAIL_FROM) existe para ambiente de teste com mock (Mailpit no e2e) — sem auth nem TLS, não aponte para um SMTP real. Com as duas configs setadas, o Resend vence (é@Primary). -
Health check:
GET /healthna porta8080(o Traefik só roteia após200). O Flyway e oJwtServicefalham-fast no startup — config errada não sobe. É o/healthflat doxadm-comum-web({status, versao, flavor, commit}, liveness, 200 fixo): o health domicronaut-managementestá desligado (endpoints.health.enabled=falsenoapplication.yml), então indicador de banco não faz o check oscilar. Ver decisão 0016. -
Deploy: salve e dispare o deploy pela UI. Acompanhe os logs até
Startup completed. Só este primeiro deploy é manual — do segundo em diante quem dispara é o CI.
Deploy de código (o fluxo normal)¶
Não há webhook do Coolify nem ponte SSH: o deploy de código passa pelo control-plane (0021), e o central deploya a si mesmo por ele (0022).
- Push em
master(ou tagv*) dispara opipeline.ymlno GitHub Actions. - O
build_nativeroda em paralelo ao gate e publica duas tags:fonte.xadm.biz/xadm/central-backend:native-amd64(móvel, a que o deploy consome) e…:<sha>-native(imutável, o alvo de rollback do smoke). O build recebe--build-arg XADM_COMMIT=<sha>, que oDockerfile.nativepromove aENVe axadm-comum-webemite comocommitno/health— é o que prova que o container no ar é o desta entrega. - Só com o gate verde (e, em tag, o
release-check), o jobdeployfazPOST $CENTRAL_DEPLOY_URL(https://central-backend.xadm.biz/api/ci/deploy— flavor-agnóstica: URL que outro sistema consome não carrega flavor de build, constituição, Entrega) comAuthorization: Bearer $CENTRAL_DEPLOY_TOKENe{"target":"native","image":"fonte.xadm.biz/xadm/central-backend:native-amd64"}. Antes do POST ele registra ocommitque está no ar — depois do POST já é o novo. - O central-atual (no ar, dentro da casa) resolve o próprio recurso no
app_deploy_targetse chama a API do Coolify localmente — essa API é restrita ao IP da casa e o runner do GitHub não a alcança; é por isso que o POST passa pelo central e não pelo runner. -
O Coolify faz rolling deploy health-gated: a imagem nova sobe, passa no
/health→ substitui; falha → a anterior segue no ar. -
O job
smokeroda depois do deploy (que é assíncrono: o POST retorna antes de o container novo servir) e é o gate de produção — ver decisão 0027 e a norma da casa. Ele afirma que ocommitdo/healthé o desta entrega e que as rotas dedocs/app.json→smoke.routesrespondem o status declarado. Reprovou → o pipeline fica vermelho, e o relatório dá o alvo de reversão…:<sha anterior>-<target>. A reversão automática está pendente no control-plane: oPOST /api/ci/deployaceita só a tag móvel e responde400 invalid_imageà imutável, então quem reverte é o operador (seção Reversão, abaixo).
A auto-referência não trava porque o central-atual está no ar no disparo e a chamada ao Coolify enfileira e retorna antes do swap do container.
Break-glass — central fora do ar
Sem central no ar não há quem receba o POST, então o deploy automático não acontece
(o job falha no curl -fsS). Suba manualmente pela UI do Coolify, de dentro da casa:
recurso native → Redeploy (ou aponte a tag imutável da imagem boa). É cenário de incidente — o
caminho normal cobre todo release.
Verificação (go-live)¶
O job smoke já roda os dois primeiros itens automaticamente (são as rotas de smoke.routes).
Os manuais abaixo servem para incidente e para o primeiro deploy, que é manual.
# 1. Serviço no ar — e QUAL entrega/flavor está no ar
curl -sf https://central-backend.xadm.biz/health | jq '{status, versao, flavor, commit}'
# status "UP"; flavor "native"|"jvm"; commit = o sha da entrega esperada.
# `commit` ausente = imagem buildada sem --build-arg XADM_COMMIT (ou lib < 0.9.0).
# `commit: "HEAD"` = imagem AINDA no nome antigo (SOURCE_COMMIT), que o Coolify sobrescreve
# em runtime. Nao e identidade: rebuilde com XADM_COMMIT (lib >= 0.9.0 ja o omite).
# 2. JWKS com alg=RS256 e o kid esperado
curl -sf https://central-backend.xadm.biz/.well-known/jwks.json | jq '.keys[0] | {alg, kid}'
# 3. Login pAbast válido → 200 + JWT scope=full
curl -sf -X POST https://auth.xadm.biz/api/auth/pabast/login \
-H "Content-Type: application/json" \
-d '{"cliente":"vantroba","id_usuario":"123","senha":"1234","senha_compl":"567"}' \
| jq '{tem_token: (.access_token != null), scope}'
# 4. Credenciais inválidas → 401
curl -s -o /dev/null -w "%{http_code}\n" -X POST https://auth.xadm.biz/api/auth/pabast/login \
-H "Content-Type: application/json" \
-d '{"cliente":"vantroba","id_usuario":"123","senha":"0000","senha_compl":"000"}'
Logs relevantes¶
INFO ... Login pAbast OK: id_usuario=... cliente=...
WARN ... Rate limit atingido: ip=...
ERROR ... Falha ao conectar ao banco
Configure alerta (Coolify/Uptime Kuma) para /health ≠ UP.
Reversão¶
- Código — rollback automático: pendente no control-plane. O job
smokereprova, calcula o alvo…:<sha anterior>-<target>(ocommitlido do/healthantes do deploy) e pede a reversão aoPOST /api/ci/deploy, que aceita só a tag móvel<target>-amd64e responde400 invalid_imageà imutável. O relatório sai comROLLBACK FALHOU no control-planee o alvo. - Código, manual (o caminho de hoje): no recurso
central-backend-native-pull, aponte a tag da imagem para<sha anterior>-nativee faça Redeploy; depois de corrigir, volte a tag paranative-amd64, senão o próximo release re-puxa a imagem antiga. Consequência operacional: a tag imutável é ativo de produção — o registry retém as dez últimas por imagem e alvo, e nunca poda a que está no ar. - Configuração: ajuste a variável e redeploy. Um deploy que não passa no
/healthnão substitui o container anterior (rolling health-gated), então config errada derruba o deploy, não o serviço. - Migration: o rollback não desfaz o schema — o Flyway CE é forward-only. A norma expand/contract cobre isso: a release N não dropa o que N-1 ainda usa, então a imagem anterior roda sobre o schema novo. Para desfazer DDL, uma migration nova.