Pular para conteúdo

Deploy (pipeline GitHub → Coolify)

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

O que é

Como o bi-transporte-xls chega em produção (excel.vantroba.xadm.biz). Todo o CI/CD roda no GitHub Actions, num workflow único — .github/workflows/pipeline.yml (norma da casa: ADR central 0027). O Forgejo (fonte.xadm.biz) é só o git de origem e o registry de imagens — não roda CI.

A imagem é buildada fora do host, no runner do GitHub: o binário native (GraalVM, do Dockerfile.native — ADR central 0033; adoção na decisão 0034). O Dockerfile JVM fica no repo como fallback, fora do build.targets. O deploy é disparado pelo control-plane do central (ADR central 0026): o pipeline faz POST https://central-backend.xadm.biz/api/ci/deploy e o central resolve qual recurso do Coolify atualizar. O Coolify só puxa a imagem pronta e troca o container com rolling update guiado pelo HEALTHCHECK — não builda nem reage a push (auto-deploy desligado).

Job O que faz
decide Liga as flags tests / docs / native conforme o evento (ver Quando usar), lê o bloco smoke e a versão do Java (toolchain.java) do docs/app.json.
gate Guardas da casa (nav-drift, toolchain × Dockerfile, placeholder aninhado, POI em native, locale pt-BR, paridade dos Dockerfiles, libs no piso…) + ./gradlew check + contrato REST + aviso de modelagem.
release-check Só em tag: confere SemVer × CHANGELOG × tag.
docs Javadoc + OpenAPI, mkdocs build --strict (também em PR) e publicação no Garage (tag → snapshot da versão; master → dev/).
build_native Em paralelo ao gate. Publica fonte.xadm.biz/xadm/vantroba-xls:native-amd64 (tag móvel) e :<sha>-native (tag imutável). É o job mais longo (~15 min).
deploy Só com gate verde e, em tag, release-check verde. Anota o commit que está no ar (alvo de rollback) e faz o POST do alvo native.
smoke Smoke de produção pós-deploy; reprovou → rollback automático (ver Reversão).

O recurso vantroba-xls-native-pull (imagem vantroba-xls:native-amd64) é o dono do domínio principal excel.vantroba.xadm.biz e responde também no próprio excel-native.vantroba.xadm.biz. O recurso jar do Coolify fica parado. O control-plane acha o recurso pelo slug da imagem, não por uuid.

Topologia

flowchart TB
    gh["GitHub Actions<br/>pipeline.yml"]
    reg[("Registry fonte.xadm.biz<br/>xadm/vantroba-xls")]
    cp["control-plane<br/>central-backend.xadm.biz"]
    coolify["Coolify"]
    traefik["Traefik<br/>excel.vantroba.xadm.biz"]
    native["vantroba-xls-native-pull<br/>excel-native.vantroba.xadm.biz"]
    pg[("PostgreSQL<br/>vantroba_transporte")]
    garage[("Garage<br/>arquivo-api.xadm.biz")]
    ps["PowerSync<br/>ps.vantroba"]

    gh -->|"push :native-amd64"| reg
    gh -->|"POST no control-plane"| cp
    cp -->|"atualiza o recurso"| coolify
    coolify -->|"puxa a imagem"| reg
    coolify --> native
    traefik --> native
    native --> pg
    native --> garage
    pg -->|"replicação lógica (bi_*)"| ps
  • Traefik termina o TLS na borda e encaminha HTTP para a porta interna 8080 — o app vê HTTP, não HTTPS.
  • O host do JDBC não é localhost: é o nome do serviço Docker do Postgres na rede interna do Coolify (ex.: postgresql18), não o host da máquina.
  • O Garage (arquivo-api.xadm.biz) guarda o binário dos .xlsx; o PowerSync (ps.vantroba) replica as tabelas bi_* do Postgres para os clientes móveis.
  • Runtime: binário native sobre ubuntu:24.04 (glibc 2.39; o builder -ol8 compila na 2.28). PostgreSQL 18+.
  • Health check /health com intervalo de 15 s (timeout 5 s, 15 tentativas) — o rolling update espera o container novo ficar healthy.

Quando usar

  • Release nova — sempre pela skill /xadm-release (nunca bump nem tag à mão): ela bumpa o gradle.properties, escreve o CHANGELOG, cria a tag anotada vX.Y.Z e faz o push. A tag v* roda o release: testes + snapshot da doc sempre, e build + deploy dos alvos do trailer Deploy: da tag anotada — sem trailer, cai no docs/app.json → build.targets (aqui, native).
  • Redeploy sem bump (republicar o mesmo código, ex. depois de falha de infra) — workflow_dispatch (passo a passo abaixo).
  • Provisionar um ambiente novo — ver Provisionar ambiente novo no Coolify.

Push em master (ou PR) não deploya: roda só gate e/ou docs, conforme os caminhos alterados (código → gate; docs/ ou mkdocs.yml → docs). O commit chore(release) em master é pulado — quem roda o release é a tag.

Pré-requisitos

  • Claude Code com a skill /xadm-release e permissão de push em fonte.xadm.biz/xadm/vantroba-bi-xls (o repo do GitHub é o espelho que o CI lê).
  • Toda lib da casa declarada no build.gradle.kts publicada no registro: a versão que só existe no mavenLocal não chega ao runner, e a /xadm-release recusa.
  • Acesso ao GitHub Actions do repo, para acompanhar o run e disparar o workflow_dispatch.
  • Secrets do repo no GitHub — confira se um job falhar por falta deles:

    Secret Usado por
    FORGEJO_USER / FORGEJO_TOKEN build_native — push da imagem no registry fonte.xadm.biz
    CENTRAL_DEPLOY_TOKEN deploy e smoke (rollback) — POST no control-plane
    GLITCHTIP_API_TOKEN smoke, camada 3 — token read-only do GlitchTip. Ausente = smoke reprova
    SMOKE_M2M_TOKEN smoke, rota m2m — o mesmo valor do VANTROBA_XLS_API_TOKEN do recurso
    SMOKE_TOKEN smoke, rota session — o mesmo valor da env SMOKE_TOKEN do recurso
    DOCS_S3_ENDPOINT / DOCS_S3_ACCESS_KEY / DOCS_S3_SECRET_KEY docs — publicação no Garage

Variáveis de ambiente (runtime, no Coolify)

Ao contrário de um app Flutter, aqui a configuração é de runtime: vive nas variáveis de ambiente do recurso no Coolify. O único valor de build é o XADM_COMMIT, que o pipeline carimba na imagem (vira o commit do /health); não há Build Argument a configurar no Coolify, porque ele não builda.

Core (obrigatórias em produção)

Variável Padrão Descrição
VANTROBA_XLS_API_TOKEN — Bearer do /api/** (POST /api/xls/processar e o reset REST). Nome pelo destino, padrão da casa (seguranca); renomeada de API_BEARER_TOKEN — ver a nota abaixo
DATASOURCES_DEFAULT_URL jdbc:postgresql://localhost:5432/vantroba_transporte URL JDBC
DATASOURCES_DEFAULT_USERNAME vantroba_transporte usuário do banco
DATASOURCES_DEFAULT_PASSWORD — senha do banco
PORT 8080 porta HTTP
APP_BASE_URL https://excel.vantroba.xadm.biz base dos links em notificações

API_BEARER_TOKEN → VANTROBA_XLS_API_TOKEN (renomeada na próxima release). Cadastre o nome novo no recurso do Coolify antes do deploy dessa release, com o mesmo valor. O BearerTokenEnv resolve em código a primeira env não-vazia: VANTROBA_XLS_API_TOKEN, depois API_BEARER_TOKEN. Sem ambas, declara app.api-token vazia para a xadm-seguranca recusar o boot em produção. Quem chama a API não é afetado. Apague a antiga somente quando todos os deploys usarem o nome novo e o smoke estiver verde; mantenha-a se ainda precisar reverter para a versão antiga.

Telegram (opcional — sem token, notificação off)

TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID.

Object storage Garage (binário do XLS)

Variável Padrão Descrição
S3_FILE_STORAGE PSQL_GARAGE PSQL | PSQL_GARAGE | GARAGE (MAIÚSCULAS)
GARAGE_ENDPOINT http://localhost:3900 em prod: Garage do arquivo-api.xadm.biz
GARAGE_REGION garage bate com garage.toml
GARAGE_ACCESS_KEY_ID / GARAGE_SECRET_ACCESS_KEY — credencial do bucket, com os nomes que o broker da Central injeta (obrigatórias em prod)
GARAGE_ACCESS_KEY / GARAGE_SECRET_KEY (dev key) par antigo: lido só sem os nomes do broker, com WARN no boot
GARAGE_BUCKET vantroba bucket dedicado
GARAGE_CRIAR_BUCKET false true cria o bucket no startup (dev/teste); prod = false

Envs S3_* antigas seguem valendo. O application.yml lê as S3_* como default das propriedades garage.*, e as GARAGE_* sobrescrevem por prioridade de fonte (env vence o YAML) — precedência GARAGE_* > S3_* > default, sem placeholder aninhado. A credencial segue o dual-read da xadm-comum-storage 0.3.0: os nomes do broker primeiro, o par antigo como fallback (decisão 0034). Com o app no ar lendo os nomes do broker (sem o WARN), apague do recurso as S3_* e o par GARAGE_ACCESS_KEY/GARAGE_SECRET_KEY. S3_FILE_STORAGE (o modo, app.storage.modo) não muda.

Desenho e modos: etapa 03.

Error tracking (GlitchTip)

Variável Padrão Descrição
SENTRY_DSN — DSN do projeto bi-transporte-xls no bug.xadm.biz — o mesmo do docs/app.json → features.glitchtip.dsn. Ausente = desligado. Confira pela chave: DSN de projeto inexistente perde todo erro em silêncio
SENTRY_ENVIRONMENT production ambiente reportado
SENTRY_TRACES_SAMPLE_RATE 0.0 amostragem de tracing

O SentryInitializer (da xadm-comum-web) roda antes do contexto Micronaut; falha de init é engolida (nunca impede o boot). O nome antigo GLITCHTIP_DSN não é lido: recurso que só tem ele sobe com o error tracking desligado, e o boot loga WARN pedindo o rename.

Não setar SENTRY_RELEASE no Coolify. O release sai sozinho do XADM_COMMIT carimbado na imagem (o sha da entrega), e o smoke pós-deploy usa firstRelease == <sha> para achar exceção nova. SENTRY_RELEASE é override do operador e ganha da lib: um vX.Y.Z ali descasa o release do sha e cega a camada 3 do smoke (decisão 0029).

Reset da replicação PowerSync (POWERSYNC_* / COOLIFY_*)

Tabela completa (o runbook resetar-powersync aponta pra cá):

Variável Padrão Descrição
POWERSYNC_MONGODB_URI — Connection string do Mongo do PowerSync. Vazia → o reset só simula o drop. O nome do DB é lido do path da URI
POWERSYNC_MONGODB_DATABASE (do path da URI) Override do nome do DB Mongo (raro)
POWERSYNC_NUKE_CONFIRMATION — Habilita o endpoint REST (a tela /admin não depende). Contrato: nome permanece "nuke"
POWERSYNC_SLOT (auto-discover) Vazio → descobre o slot do Mongo (sync_rules com state='ACTIVE'). Setar explícito só pra forçar/recovery em PG compartilhado
POWERSYNC_PLUGIN pgoutput Plugin de replicação lógica do slot
POWERSYNC_BOOTSTRAP_WAIT 60 Segundos de espera extra pós-restart, antes da amostra final, pro bootstrap do PowerSync assentar
COOLIFY_API_TOKEN — Token Coolify (escrita/deploy). Setá-lo liga o restart automático (estratégia coolify); sem ele o restart é manual
POWERSYNC_COOLIFY_NAME — Nome exato do recurso PowerSync no Coolify (ex.: ps.vantroba). NAME ou UUID
POWERSYNC_COOLIFY_UUID — Alternativa ao _NAME: UUID do recurso
COOLIFY_API_URL https://coolify.xadm.biz Em prod no mesmo daemon Docker do Coolify: http://coolify:8080 (HTTP interno — evita PKIX path building failed do hairpin NAT)
POWERSYNC_HEALTH_URL — URL de health-check do PowerSync conferida pós-restart
POWERSYNC_RESTART_TIMEOUT 120 Timeout (s) do polling do deployment Coolify

Postgres: a role do app precisa do atributo REPLICATION (o reset dropa/cria o slot). Uma vez, como superuser: ALTER ROLE <role_do_app> WITH REPLICATION;.

Login das views (AUTH_*)

Runbook próprio com a tabela: google-auth.

Smoke de produção (SMOKE_TOKEN)

Variável Padrão Descrição
SMOKE_TOKEN — Liga o seam POST /smoke/session da xadm-seguranca, que troca o token (cabeçalho X-Smoke-Token) por sessão de um principal smoke só-leitura. Vazia = seam desligado, e a rota session do smoke reprova. Mesmo valor do secret SMOKE_TOKEN do GitHub

Passos

Release

  1. Rode /xadm-release no Claude Code do repo.
  2. Acompanhe o run do workflow pipeline no GitHub Actions, disparado pela tag vX.Y.Z: gate, release-check e build_native em paralelo → deploy → smoke. O POST do deploy é assíncrono (responde antes de o container novo servir) — quem confirma a entrega no ar é o smoke.
  3. Smoke verde = produção confirmada. Vermelho: veja Reversão.

Redeploy sem bump

GitHub Actions → workflow pipeline → Run workflow, escolhendo como ref a tag vigente (não master, que pode ter código ainda sem release) e marcando native. Deixe tests marcado (default): sem ele o build roda, mas o deploy sai pulado — deploy só com gate verde.

Provisionar ambiente novo no Coolify

  1. Banco (no Postgres compartilhado, como superuser) — criar antes do app:
    CREATE DATABASE vantroba_transporte;
    CREATE USER vantroba_transporte WITH PASSWORD '<senha-forte>';
    GRANT ALL PRIVILEGES ON DATABASE vantroba_transporte TO vantroba_transporte;
    \c vantroba_transporte
    GRANT ALL ON SCHEMA public TO vantroba_transporte;
    
    Se for usar reset da replicação, garantir o atributo REPLICATION na role e a existência do powersync_role (ver decisão 0013).
  2. Recurso Docker Image (pull-only, auto-deploy desligado), com o nome canônico <slug>-<target>-pull: vantroba-xls-native-pull na imagem fonte.xadm.biz/xadm/vantroba-xls:native-amd64. Não há webhook nem secret por recurso: o control-plane acha o recurso pelo slug da imagem (reconcile do central — ver deploy na infraestrutura).
  3. Variáveis — todas as acima.
  4. Volume: mapear /app/data/execucoes (WORKDIR /app + app.logs.dir: data/execucoes): é o histórico por processamento que a UI exibe, dado do app — nunca em /app/logs.
  5. Health check: caminho /health, porta 8080 (obrigatório — o rolling update espera ficar healthy).
  6. Domínio: excel.vantroba.xadm.biz (principal) e excel-native.vantroba.xadm.biz no mesmo recurso. Procedimento e armadilhas na infraestrutura da casa.

Flyway: o histórico fica em bi_transporte_flyway_schema_history. As migrations rodam no startup. Ao migrar de um schema antigo sem prefixo, rodar ALTER TABLE bi_flyway_schema_history RENAME TO bi_transporte_flyway_schema_history; antes do deploy, senão o Flyway recria a tabela vazia e re-aplica tudo.

Conformidade e retenção: os dados sensíveis do Excel ficam no PostgreSQL e no object storage; usar HTTPS sempre. Os logs de execução em disco (app.logs.dir) não substituem auditoria fiscal — conservar as políticas de backup do banco e do volume de logs.

Verificação

  • Job smoke verde = produção confirmada. Ele roda depois do deploy e confere sozinho a identidade (o commit do /health é o sha entregue), as rotas críticas do bloco smoke do docs/app.json (/health, a listagem do reset com o Bearer e /processamentos com a sessão do seam) e nenhuma exceção nova no GlitchTip. Manifesto e porquê na decisão 0029; a norma, em smoke de produção.
  • Conferência manual (opcional): curl https://excel.vantroba.xadm.biz/health → {"status":"UP","versao":"X.Y.Z","flavor":"native","commit":"<sha>"}.
  • GET /processamentos abre (login em prod).
  • Logs do container no Coolify sem erro de Flyway/datasource e sem o WARN de credencial antiga do Garage.

Reversão

Automática (smoke pós-deploy). Se o smoke reprova, o pipeline reverte sozinho para a tag imutável fonte.xadm.biz/xadm/vantroba-xls:<sha anterior>-native (o sha anterior é o commit que o /health mostrava antes do deploy), repola o /health até confirmar a volta e deixa o run vermelho. Se o relatório disser "rollback NÃO confirmado", produção pode estar quebrada: siga para a reversão manual. Quando o smoke não reverte (outro deploy assumiu, falta de alvo, defeito de configuração como GLITCHTIP_API_TOKEN ausente): ver a norma do smoke.

Primeiro deploy após adotar o smoke: o /health no ar ainda não tinha commit (lib < 0.9.0), então esse deploy não tem alvo de rollback — se reprovar, a reversão é manual. Dali em diante o rollback automático vale.

Não pode a tag <sha>-native que está no ar — é o alvo do rollback; apagá-la do registry transforma a reversão em "não confirmada" no pior momento.

Manual, quando preciso:

  1. workflow_dispatch do pipeline tendo como ref a tag anterior boa, com native marcado — rebuilda e redeploya aquele código pelo caminho normal, com gate e smoke.
  2. Break-glass (GitHub ou control-plane fora do ar): redeploy pelo painel do Coolify, no recurso native, a partir de uma imagem <sha>-native boa. É fora do fluxo — volte ao pipeline no deploy seguinte.

Fallback JVM (defeito que só o binário native tem): registre o motivo numa decisão do app, ponha jar no build.targets, restaure do template do pipeline.yml o build_jar e o step "Deploy jar", e religue o recurso vantroba-xls-jar-pull (imagem :jar-amd64) com as mesmas variáveis.

A V17 (histórico em data/execucoes) aguenta a volta: a release anterior lê os caminhos novos pelo volume, mas grava os logs que produzir em /app/logs, fora dele.

Migrations Flyway não revertem (nem no rollback do smoke) — se a versão nova introduziu migration incompatível, planejar a correção no banco antes de voltar.