Pular para conteúdo

Operação no Coolify

Como criar, configurar e operar uma aplicação no Coolify da X-Adm. Os exemplos usam nomes do BI Vantroba; troque pelos do seu app.

Como a rede funciona

O Traefik (proxy do Coolify) termina o TLS na borda e encaminha para a porta interna do container. O PostgreSQL roda em outro projeto do Coolify (000-compartilhado, recurso postgresql18) — o hostname JDBC é o nome do serviço Docker na rede interna, nunca localhost nem IP externo.

flowchart TD
    I[Internet] -- "HTTPS 443" --> T[Traefik]
    T -- "HTTP porta interna" --> A[Container do app<br/>projeto: vantroba-bi]
    A -- "JDBC 5432, rede interna" --> P[(postgresql18<br/>projeto: 000-compartilhado)]

Criar uma aplicação nova

  1. Projects → selecionar/criar o projeto → + New Resource.
  2. Deploy from Git Repository (o Coolify builda o Dockerfile do repo). Build Pack: Dockerfile. O repo tem que trazer o Dockerfile + .dockerignore (constituição §3) — não há buildpack que adivinhe a stack, e o ci.yml não builda imagem: aqui é o primeiro lugar onde eles rodam. Derive-os dos templates.
  3. Domains → adicionar https://<subdominio>.xadm.biz (DNS A apontando para o servidor; o certificado Let's Encrypt é automático).
  4. Environment Variables → configurar; marcar senhas e tokens como Secret (ficam ocultos nos logs e na UI).
  5. Storages → mapear volumes para o que precisa sobreviver a redeploy (ex.: /app/data, uploads). Log de servidor não entra aqui: por engenharia/java o servidor loga em stdout e o Coolify captura — volume em /app/logs é o arquétipo errado. O diretório tem que existir na imagem com dono app (mkdir -p + chown antes do USER app, ver o Dockerfile): volume nomeado herda o dono do diretório da imagem, e se o diretório não existir ele nasce root — o app não escreve. Bind mount não herda dono nenhum: aí o dono no host é que precisa ser 1001:1001.
  6. Health check: path /health, porta interna do app, intervalo 30s.
  7. Deploy e aguardar build + health check verde.

Build Arguments × Environment Variables (pega-pé recorrente)

O Coolify tem dois lugares para valores, e eles entram em momentos diferentes do ciclo — trocar os lados é falha silenciosa.

  • Build Argument (+ marcar "Available at Buildtime"): chega ao docker build como ARG. Use para tudo que o build consome e embute na imagem — ARG FLUTTER_VERSION, SOURCE_COMMIT e todo valor que o Flutter embarca via --dart-define (ex. SENTRY_DSN, endpoint de release). Um --dart-define lê a variável na hora do build; se ela só existe como env de runtime, chega vazia no bundle e o app sobe sem ela, sem erro.
  • Environment Variable (runtime): chega ao processo em execução, não ao build. Use para o que o app lê no startup — URL do JDBC, secrets lidos em runtime, flags de execução.

Regra de bolso: o build precisa? → Build Argument. Só o processo lê? → Environment Variable. Erra nos dois sentidos: valor de build configurado só como runtime não chega ao build; e segredo de runtime marcado como build arg vaza como ARG na imagem (foi o caso dos DOCS_S3_*). Não marque "Available at Buildtime" no que não for de build.

Carimbar o commit do deploy: NÃO leia o .git/HEAD no build

Tentação que não funciona: ler .git/HEAD no Dockerfile para carimbar o commit (ex. /commit.txt). O Coolify não manda o .git no contexto de buildCOPY .git/HEAD falha com "/.git/HEAD": not found e quebra o deploy (comprovado no central, jun/2026). Nem a negação .dockerignore (.git/* + !.git/HEAD) resolve, porque o .git simplesmente não está no contexto.

Se você precisa do SHA na imagem, o caminho é o Build Argument: marque SOURCE_COMMIT como Available at Buildtime no Coolify (mesma mecânica da seção acima). Sem isso, o ARG SOURCE_COMMIT=dev carimba dev — cosmético, não é exigência da constituição (o contrato de runtime é a versão legível, não o SHA — versionamento).

Dockerfile que lê docs/app.json: case a exceção no .dockerignore

Se o Dockerfiledocs/app.json no build — a ponte de config da Central de Apps: Flutter injeta o client-grade via jq … docs/app.json--dart-define, e um servidor pode copiar docs/app.json para o classpath e lê-lo em runtime — lembre que o .dockerignore tipicamente exclui docs/ (é peso morto para compilar). Sem a exceção, o arquivo não entra no contexto de build: o jq/COPY falha ou lê versão velha e o deploy quebra. As duas metades são uma decisão só e viajam no mesmo PR — a ponte no Dockerfile e a exceção no .dockerignore:

docs
!docs/app.json

Ao contrário do .git/HEAD acima, aqui a negação resolve: docs/app.json é committado e está no contexto — é só o .dockerignore que o exclui. (Lá o .git nem chega ao contexto do Coolify, então !.git/HEAD não adianta.)

Banco no PostgreSQL compartilhado

Criar banco e usuário dedicados por app, dentro do postgresql18 (terminal do Coolify ou psql):

CREATE DATABASE meu_app;
CREATE USER meu_app WITH PASSWORD 'senha-forte';
GRANT ALL PRIVILEGES ON DATABASE meu_app TO meu_app;
\c meu_app
GRANT ALL ON SCHEMA public TO meu_app;  -- PostgreSQL 15+

Apps que usam PowerSync com "nuke" da replicação precisam de ALTER ROLE meu_app WITH REPLICATION; (não dá superuser; só gerencia slots de replicação).

API do Coolify de dentro de um container

Quando um app que roda no mesmo daemon Docker do Coolify precisa chamar a API do Coolify, usar a URL interna http://coolify:8080 (DNS da network Docker). A URL pública falha em produção: o certificado wildcard pode não estar na truststore da JVM (PKIX path building failed) e o hairpin NAT cai fora da allowlist (HTTP 403). A URL pública fica só para dev local.

Setar env/secret programaticamente (guard de deploy da Central de Apps)

A API expõe upsert em lote de variáveis por aplicação — PATCH /api/v1/applications/{uuid}/envs/bulk (Bearer token; POST .../envs cria uma, PATCH .../envs atualiza uma). "Secret" não é um tipo à parte: é uma env marcada como Secret (fica oculta em logs/UI). Cada variável tem os flags Build Variable e Runtime Variable (ambos ligados por default) — valor que o build embarca (--dart-define, ARG) exige Build Variable ligado (ver Build Arguments acima). É esse endpoint que o guard de deploy da Central de Apps usa para injetar os secrets de build no recurso; o payload exato é confirmado na implementação do broker no auth. Ref: PATCH Update Envs (Bulk).

Atualizar, rollback e logs

  • Deploy automático — dois modelos, escolha declarada por repo: (a) auto-deploy no push (webhook de repo do Forgejo, evento Push) — todo push na branch rebuilda, mesmo com o CI vermelho (o único gate é o próprio build do Dockerfile); o aviso de falha do CI vem do webhook nativo da org no Telegram (Forgejo ≥12, fora do YAML — ver Forgejo). (b) deploy gated pelo CI — auto-deploy desligado no painel e o workflow ganha um job deploy (needs: validar, só em push de master) que dispara o webhook de deploy do Coolify via curl com COOLIFY_WEBHOOK_URL/COOLIFY_API_TOKEN (secrets do repo); CI vermelho = site/app antigo continua no ar. O central de docs usa (a): a faixa 3 (2026-07-05) chegou a adotar (b), mas foi revertido em 2026-07-07 por falta da config (secrets + auto-deploy off) — (b) fica como melhoria futura (o job comentado está no build-site.yml do central; ver o backlog).
  • Manual: botão Redeploy no painel do recurso.
  • Rollback: Deployments → escolher revisão anterior → Rollback (o Coolify guarda as últimas imagens).
  • Logs: painel Logs do recurso (stdout/stderr do container); logs de aplicação persistidos ficam no volume mapeado.

Checklist pré-go-live

  • [ ] Banco e usuário criados no postgresql18
  • [ ] URL do banco apontando para o hostname interno (não localhost/IP)
  • [ ] DNS do domínio apontando para o servidor Coolify
  • [ ] Variáveis configuradas; segredos marcados como Secret
  • [ ] Valores de build (--dart-define, ARG) como Build Argument; runtime como Environment Variable (não trocar os lados)
  • [ ] Volume de dados/logs mapeado
  • [ ] Health check verde
  • [ ] Teste manual do endpoint público (curl -I https://.../health)