Pular para conteúdo

Provisionar o monitor de host/apps do Coolify

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

Habilita os endpoints GET /api/admin/monitor/host e GET /api/admin/monitor/apps do central-backend (Fase 1, read-only — decisão 0019). Três peças de infra: o proxy do socket (serviço Coolify à parte), mounts do host e env no recurso do central. Tudo leitura — nenhuma ação destrutiva.

⚠️ Segurança: o socket do Docker é equivalente a root no host. O central nunca monta o socket cru — fala só com o proxy, que libera uma allowlist mínima de rotas GET. Um bug com o socket cru = comprometimento total de produção.

Estado em 2026-09-11: o proxy está no ar e verificado em produção (§1). Falta ligar o central a ele — mounts + env + redeploy do central-backend-native-pull (§4), que depende do release do central com o /system/df?type=build-cache (commit beb542b).

1. Proxy do socket — wollomatic/socket-proxy (allowlist por rota)

Por que não o tecnativa/docker-socket-proxy: lá a flag CONTAINERS=1 é regra por prefixo (^(/v[\d\.]+)?/containers) e libera todo GET sob /containers — /containers/{id}/json expõe o env de todos os containers (inclusive AUTH_PRIVATE_KEY), além de archive, export e logs. O wollomatic/socket-proxy aplica regex ancorada por rota: libera só as 3 rotas que o monitor lê.

O serviço em produção:

Item Valor
Serviço Coolify monitor-docker-proxy (uuid uuatsojwo5fvedw7qw5sqm1q)
Projeto / ambiente 000-compartilhado / production
Rede "Connect To Predefined Network" ligado (rede coolify); sem porta publicada
Host estável p/ o central http://docker-socket-proxy-uuatsojwo5fvedw7qw5sqm1q:2375
Imagem ghcr.io/wollomatic/socket-proxy:1.13.1@sha256:e78e68ba3ea4a6bf8ee47865ac1d1f98d525c3660df948554e766283c40d3f4b (digest do índice multi-arch)
# docker-compose do serviço monitor-docker-proxy (Coolify)
services:
  docker-socket-proxy:
    image: ghcr.io/wollomatic/socket-proxy:1.13.1@sha256:e78e68ba3ea4a6bf8ee47865ac1d1f98d525c3660df948554e766283c40d3f4b
    user: "65534:987"          # nobody : GID do docker.sock no bi-ubuntu
    read_only: true
    cap_drop: [ALL]
    security_opt:
      - no-new-privileges:true
    mem_limit: 32m
    mem_reservation: 16m
    cpus: 0.25
    command:
      - "-listenip=0.0.0.0"
      - "-proxyport=2375"
      - "-allowfrom=10.0.1.0/24,fde3:9396:7e00::/64"
      - "-allowGET=/(v[0-9.]+/)?(containers/json|containers/[0-9a-f]{64}/stats|system/df)"
      - "-allowhealthcheck"
    healthcheck:
      test: ["CMD", "./healthcheck"]
    volumes:
      # o :ro não restringe a API (é um socket) — quem restringe é o -allowGET
      - /var/run/docker.sock:/var/run/docker.sock:ro
    # NÃO publique porta — só a rede interna do Coolify
  • Qualquer método ≠ GET é recusado (405): não há -allowPOST/-allowDELETE etc.
  • Query string não entra no casamento da rota: ?stream=false e ?type=build-cache passam.
  • GID 987 é o dono do /var/run/docker.sock no bi-ubuntu. Em outro host (ou após reinstalar o Docker), confira com stat -c %g /var/run/docker.sock e ajuste o user:.

⚠️ Pegadinha — allowfrom precisa da sub-rede IPv6. A rede coolify é dual-stack e o central chega ao proxy por IPv6. Com -allowfrom só na sub-rede IPv4 (10.0.1.0/24), tudo dava 403 forbidden IP. Por isso as duas: 10.0.1.0/24,fde3:9396:7e00::/64.

Verificado em 2026-09-11 (de dentro do container do central, na rede coolify):

Requisição Esperado
GET /containers/json 200
GET /containers/<id>/stats?stream=false 200
GET /system/df?type=build-cache 200
GET /containers/<id>/json, /containers/<id>/archive, /containers/<id>/logs, /containers/<id>/export 403
GET /info, /system/info, /images/json 403
path traversal GET /containers/json/../<id>/json 403
versão prefixada GET /v1.55/containers/<id>/json 403
POST /containers/create 405

O log do proxy registra cada recusa com reason="path not allowed" ou reason="method not allowed".

2. Mounts do host (superfície mínima)

No recurso central-backend-native-pull (Coolify → Persistent Storage → Directory Mount):

Host No container Para quê
/proc /host/proc MemTotal/MemAvailable/SwapTotal/SwapFree (mem e swap)
/srv/xadm-probe /host/probe statvfs do filesystem de / (disco % cheio)
  • /srv/xadm-probe é um diretório vazio dedicado (0555 root:root, já criado no host) — não expõe nada; substitui o /etc/os-release do desenho original. O statvfs reporta o filesystem que contém o probe: ele tem de estar no filesystem de / (confira com findmnt -T /srv/xadm-probe).
  • Não monte / inteiro: o par acima dá as métricas com exposição mínima (/proc é virtual).
  • O Directory Mount é bind rw — a API do Coolify não tem mount read-only. A pré-condição é o container não-root: o Dockerfile.native roda como USER app (uid 1001), e o monitor só lê. Nunca monte o /proc do host num container root: o bind não herda o read-only que o Docker aplica a /proc/sys e /proc/sysrq-trigger (coolify, Criar uma aplicação).

3. Env vars (recurso central-backend-native-pull)

Variável Valor Default
DOCKER_PROXY_URL http://docker-socket-proxy-uuatsojwo5fvedw7qw5sqm1q:2375 vazio → fontes docker/docker_df viram erro
HOST_MOUNT_PATH ponto do mount do host /host (não precisa setar)

Sem DOCKER_PROXY_URL as fontes docker/docker_df degradam (200 parcial); sem o mount /host/proc, degradam proc; sem o /host/probe, degrada disco. Cada fonte falha isolada, com motivo.

4. Ligar no central (pendente)

Pré-requisito: release do central com o commit beb542b (/system/df?type=build-cache — sem o type o daemon percorre todos os volumes do host, ~15 GB, a cada chamada, e o timeout estoura).

  1. No central-backend-native-pull: os dois Directory Mounts do §2 e a env DOCKER_PROXY_URL do §3.
  2. Redeploy.
  3. Verifique (§5): /monitor/host com fontes.docker_df:"ok" e /monitor/apps com fontes.docker:"ok".
  4. Meça o tempo do /monitor/apps — ele chama /containers/{id}/stats sequencialmente, um por container running; registre o número para decidir se precisa paralelizar.

Rollback: tire a env e os mounts do central-backend-native-pull e redeploy (as fontes voltam a degradar, 200 parcial). Para remover o proxy: pare/apague o serviço monitor-docker-proxy.

5. Verificar

Com um JWT admin (xadm_admin) ou o shared secret do integrador:

curl -s -H "Authorization: Bearer <ADMIN_JWT>" https://central-backend.xadm.biz/api/admin/monitor/host | jq '.fontes'
curl -s -H "Authorization: Bearer <ADMIN_JWT>" https://central-backend.xadm.biz/api/admin/monitor/apps | jq '.fontes'
curl -s -H "Authorization: Bearer <ADMIN_JWT>" https://central-backend.xadm.biz/api/admin/monitor/apps | jq '.apps[] | select(.limits_memory=="0") | .nome'

O último lista os apps sem limite de memória (o preditor do freeze). Cada resposta é 200; a fonte que estiver fora aparece em .fontes/.erros com o motivo, sem derrubar o resto.

Notas

  • Read-only, sob demanda: nenhum daemon, nenhuma ação destrutiva. Fase 2 (limpeza/restart com whitelist + confirmação + auditoria) é desenho à parte — e exigiria rever a allowlist do proxy.
  • O monitor não estressa a VM: timeout curto (~3 s) por chamada ao socket, só containers running; o proxy tem teto de 32 MB / 0,25 CPU.
  • CORS: os endpoints herdam o AdminApiCorsFilter (origin central.xadm.biz liberado) por estarem sob /api/admin/**.