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/hosteGET /api/admin/monitor/appsdocentral-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/-allowDELETEetc. - Query string não entra no casamento da rota:
?stream=falsee?type=build-cachepassam. - GID
987é o dono do/var/run/docker.socknobi-ubuntu. Em outro host (ou após reinstalar o Docker), confira comstat -c %g /var/run/docker.socke ajuste ouser:.
⚠️ Pegadinha —
allowfromprecisa da sub-rede IPv6. A redecoolifyé dual-stack e o central chega ao proxy por IPv6. Com-allowfromsó na sub-rede IPv4 (10.0.1.0/24), tudo dava403 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-releasedo desenho original. Ostatvfsreporta o filesystem que contém o probe: ele tem de estar no filesystem de/(confira comfindmnt -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.nativeroda comoUSER app(uid 1001), e o monitor só lê. Nunca monte o/procdo host num container root: o bind não herda o read-only que o Docker aplica a/proc/syse/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).
- No
central-backend-native-pull: os dois Directory Mounts do §2 e a envDOCKER_PROXY_URLdo §3. - Redeploy.
- Verifique (§5):
/monitor/hostcomfontes.docker_df:"ok"e/monitor/appscomfontes.docker:"ok". - Meça o tempo do
/monitor/apps— ele chama/containers/{id}/statssequencialmente, 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(origincentral.xadm.bizliberado) por estarem sob/api/admin/**.