Introducción
Tengo una VM en Oracle Cloud con varios servicios en Docker (el blog, Nginx Proxy Manager, Umami y WireGuard) y un Intel NUC en casa que hace de servidor doméstico. Hasta ahora, saber cuánta CPU, memoria o disco usaba cada máquina significaba entrar por SSH y ejecutar docker stats o htop. Funciona, pero no deja histórico ni avisa de nada.
Quería algo sencillo: un solo panel con las dos máquinas, el estado de los contenedores y un histórico. La opción clásica es Prometheus + Grafana, pero para dos máquinas me pareció demasiado montaje. Así llegué a Beszel.
En este artículo explico qué es Beszel, cómo se compara con Prometheus + Grafana, y los pasos exactos que seguí para instalar el servidor (Hub) en Oracle Cloud y los agentes en la VM y en el NUC. También cuento lo que me tocó ajustar en el camino.
Conceptos básicos
Si es tu primera vez con monitoreo, estos tres conceptos te ayudarán a seguir el resto:
- Servidor y agente. En muchas herramientas de monitoreo hay un servidor central que guarda los datos y muestra los paneles, y un programa pequeño (agente) instalado en cada máquina que mides. El agente recoge las métricas y las envía al servidor. En Beszel, el servidor se llama Hub.
- Proxy inverso. Es el programa que recibe las visitas a tu dominio (por ejemplo
beszel.tudominio.com) y las reenvía al servicio correcto dentro de tu servidor. Yo uso Nginx Proxy Manager. - WebSocket. Es una conexión que se mantiene abierta entre dos programas para intercambiar datos continuamente. Los agentes de Beszel la usan para enviar sus métricas al Hub.
Qué es Beszel
Según su documentación, Beszel es una plataforma ligera de monitoreo de servidores que incluye estadísticas de Docker, datos históricos y alertas. Tiene interfaz web, configuración sencilla, múltiples usuarios, autenticación OAuth, API y respaldos automáticos.
Se compone de dos piezas:
- Hub: una aplicación web construida sobre PocketBase. Es el panel donde ves los sistemas conectados, el histórico y las alertas.
- Agent: un programa que corre en cada máquina que quieres monitorear y envía las métricas al Hub.
Entre las métricas que soporta están CPU, memoria, disco, red, carga media, temperatura, estado y consumo de cada contenedor Docker o Podman, datos S.M.A.R.T. de los discos, y monitores de red (ICMP, TCP, HTTP y DNS).
Cómo se conectan el Hub y el Agent
Hay dos modos, y entender la diferencia evita muchos dolores de cabeza:
| Modo | Quién inicia la conexión | Qué necesita |
|---|---|---|
| WebSocket | El Agent se conecta al Hub | Variable HUB_URL en el Agent. No hace falta abrir puertos en la máquina del Agent |
| SSH | El Hub se conecta al Agent | El Agent escucha en el puerto 45876, que debe ser alcanzable desde el Hub |
Para mi NUC, que está detrás del router de mi proveedor de internet sin poder abrir puertos fácilmente, el modo WebSocket es justo lo que necesitaba.
Beszel frente a Prometheus + Grafana
Prometheus es un conjunto de herramientas de monitoreo y alertas de código abierto. Su servidor recolecta y almacena métricas como series de tiempo, y las obtiene consultando (pull) por HTTP a los objetivos que le configuras. Su ecosistema tiene varios componentes, casi todos opcionales: exportadores, un gestor de alertas (Alertmanager) y otros. Para visualizar los datos se usa habitualmente Grafana, que permite consultar, visualizar y generar alertas sobre métricas, logs y trazas, vengan de donde vengan.
Son herramientas muy potentes, pero resuelven un problema más amplio que el mío.
| Aspecto | Beszel | Prometheus + Grafana |
|---|---|---|
| Piezas a instalar | Hub + un Agent por máquina | Servidor Prometheus, exportadores para el sistema y los contenedores (la documentación de Prometheus tiene guías para Node Exporter y cAdvisor), Grafana y, opcionalmente, Alertmanager |
| Métricas de Docker | Incluidas en el Agent | Requieren un exportador adicional |
| Paneles | Listos al instalar | Los construyes o importas desde la biblioteca de la comunidad |
| Recolección | El Agent envía al Hub (o el Hub consulta al Agent por SSH) | Prometheus consulta a los objetivos |
| Alertas | Configurables desde la interfaz, con decenas de servicios de notificación | Reglas en Prometheus con Alertmanager, o alertas de Grafana |
| Consultas y flexibilidad | Limitada a las métricas que Beszel soporta | PromQL y un modelo de datos multidimensional, pensado para sistemas complejos |
| Logs y trazas | No es su objetivo | Grafana puede explorarlos si añades las fuentes de datos adecuadas |
| Curva de aprendizaje | Baja | Más piezas que aprender y mantener |
El propio proyecto Beszel afirma ser más pequeño y consumir menos recursos que las soluciones líderes. No hice una comparación de consumo, así que no pondré cifras ajenas. Lo que sí puedo mostrar es lo que consume en mi caso, según su propio panel: el Hub usa alrededor de 17 MB de memoria y los Agents entre 13 y 15 MB (medido en un momento puntual, no es un promedio).
Cuándo conviene cada uno
- Beszel encaja si tienes pocas máquinas, quieres ver rápido CPU, memoria, disco y contenedores, y prefieres instalar poco y mantener menos.
- Prometheus + Grafana encaja si necesitas métricas propias de tus aplicaciones, consultas avanzadas, muchas fuentes de datos o un ecosistema de observabilidad completo con logs y trazas. La documentación de Prometheus destaca su fortaleza en arquitecturas dinámicas orientadas a servicios.
No son excluyentes: puedes empezar con Beszel y crecer después si lo necesitas.
Arquitectura
Este es el diseño final:
INTERNET
│
┌────▼────┐
│Cloudflare│ (DNS + proxy, HTTPS)
└────┬────┘
│
┌───────────────▼────────────────┐
│ VM en Oracle Cloud │
│ │
│ Nginx Proxy Manager │
│ (SSL + Websockets) │
│ │ 172.17.0.1:8090 │
│ ▼ │
│ Beszel Hub ◄─ WebSocket ─ Agent local
│ (172.17.0.1) │
│ Docker socket │
└────────────▲────────────────────┘
│ WebSocket saliente (HTTPS)
│
┌───────┴────────┐
│ Intel NUC │
│ Agent + Docker│
└────────────────┘
Decisiones de diseño:
- El Hub corre en la VM de Oracle y se publica con Cloudflare y Nginx Proxy Manager, igual que mis otros servicios.
- El Agent de la VM se conecta al Hub por WebSocket a través de
172.17.0.1:8090, y deja el socket Unix como respaldo (modo SSH). Así no se abre el puerto45876. - El Agent del NUC se conecta de forma saliente a la URL pública del Hub. No abro puertos en casa ni uso VPN para esto.
- No hay Prometheus, Grafana ni base de datos externa.
Requisitos previos
- Una VM con Docker y Docker Compose. En mi caso, Oracle Linux en ARM (
aarch64). - Nginx Proxy Manager funcionando y publicando servicios con SSL.
- Un dominio en Cloudflare y un subdominio libre para el Hub (yo usé
beszel). - Una segunda máquina con Docker para el Agent remoto (mi NUC con EndeavourOS).
Instalación del Hub en Oracle Cloud
1. Verificar el entorno
Antes de instalar, comprobé la arquitectura, la gateway de la red Docker por defecto, que el puerto 8090 estuviera libre y el estado de SELinux:
uname -m # aarch64
docker network inspect bridge | grep Gateway # "Gateway": "172.17.0.1"
ss -tlnp | grep 8090 # sin salida: el puerto está libre
getenforce # Enforcing
Dos datos de aquí condicionan el resto: la gateway 172.17.0.1 (por donde Nginx Proxy Manager alcanzará el Hub) y SELinux en modo Enforcing.
2. Crear la carpeta y el docker-compose.yml
Entra en la carpeta antes de crear el archivo, para no dejarlo en el directorio equivocado:
mkdir -p ~/Proyectos/beszel
cd ~/Proyectos/beszel
vim docker-compose.yml
Primero levanté solo el Hub, porque todavía no tengo la KEY ni el TOKEN del Agent:
services:
beszel:
image: henrygd/beszel:latest
container_name: beszel
restart: unless-stopped
environment:
APP_URL: https://beszel.tudominio.com
ports:
- 172.17.0.1:8090:8090
volumes:
- ./beszel_data:/beszel_data:z
- ./beszel_socket:/beszel_socket:z
Qué cambié respecto al ejemplo oficial y por qué:
APP_URLapunta a mi dominio público. La documentación recomienda definirla porque se usa en enlaces de notificaciones y en la configuración que genera para los agentes.172.17.0.1:8090:8090publica el puerto solo en la gateway de Docker. Nginx Proxy Manager puede llegar ahí, pero el puerto no queda expuesto en la IP pública de la VM. El ejemplo oficial usa8090:8090, que lo publica en todas las interfaces.:zen los volúmenes le pide a Docker que etiquete las carpetas para SELinux. Lo agregué de forma preventiva por tener SELinux enEnforcing; no probé sin él, así que no sé si era imprescindible.
docker compose up -d
docker logs beszel --tail 20
curl -I http://172.17.0.1:8090 # debe responder 200
3. DNS en Cloudflare
Creé un registro A para el subdominio apuntando a la IP de la VM, con el proxy de Cloudflare activado (nube naranja), igual que el resto de mis subdominios:
| Tipo | Nombre | Contenido | Proxy |
|---|---|---|---|
| A | beszel | <IP_PÚBLICA_DE_LA_VM> | Proxied |
4. Proxy Host en Nginx Proxy Manager
En NPM creé un Proxy Host con estos valores:
- Domain Names:
beszel.tudominio.com - Scheme:
http - Forward Hostname / IP:
172.17.0.1 - Forward Port:
8090 - Block Common Exploits: activado
- Websockets Support: activado
El Websockets Support es obligatorio: la documentación indica que el proxy debe reenviar conexiones WebSocket para que los agentes puedan conectarse. Sin eso, el Agent del NUC no funcionaría.
En la pestaña SSL solicité un certificado nuevo de Let’s Encrypt y activé Force SSL.
5. Crear el usuario administrador
Al abrir https://beszel.tudominio.com por primera vez, Beszel pide crear la cuenta de administrador. Después de crearla ya puedes entrar al panel.
Agent local (VM de Oracle)
Quiero monitorear también la propia VM, así que necesita su Agent. Como el Hub y el Agent están en la misma máquina, la documentación recomienda conectarlos con un socket Unix, porque localhost no funciona entre contenedores en redes distintas.
1. Obtener la KEY y el TOKEN
En el panel, pulsa Agregar sistema (Add System) y rellena:
- Nombre: el que quieras (yo puse
bta-1). - Host / IP:
/beszel_socket/beszel.sock
El diálogo muestra la KEY y el TOKEN. Cópialos y no pulses todavía el botón de agregar.
2. Agregar el Agent al compose
Añadí este servicio debajo del Hub, en el mismo archivo:
beszel-agent:
image: henrygd/beszel-agent:latest
container_name: beszel-agent
restart: unless-stopped
network_mode: host
volumes:
- ./beszel_agent_data:/var/lib/beszel-agent:z
- ./beszel_socket:/beszel_socket:z
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
LISTEN: /beszel_socket/beszel.sock
HUB_URL: http://172.17.0.1:8090
TOKEN: "<TOKEN>"
KEY: "<KEY>"
Notas:
network_mode: hostes necesario para que el Agent lea las estadísticas de red de la máquina.LISTENapunta al socket Unix compartido con el Hub. Es el canal de respaldo (modo SSH): el Agent solo lo usa si falla la conexión WebSocket.HUB_URL: el ejemplo oficial usahttp://localhost:8090. Como mi puerto está publicado en172.17.0.1, lo cambié a esa dirección.- El montaje del socket de Docker (
docker.sock) le permite al Agent leer el estado de los contenedores. Más abajo explico por qué conviene tratarlo con cuidado. KEYyTOKENaparecen aquí como marcadores. LaKEYes la clave pública del Hub; elTOKEN, en cambio, autentica al Agent, así que trátalo como un secreto: no lo subas a un repositorio ni lo muestres en capturas. Si prefieres no dejarlo en el compose, Beszel aceptaTOKEN_FILEyKEY_FILEpara leer ambos valores desde archivos protegidos.
docker compose up -d
docker logs beszel-agent --tail 30
3. Confirmar
Vuelve al diálogo y pulsa Agregar sistema. Tras unos segundos, el sistema debe pasar a verde y mostrar los contenedores de la VM.
En los logs del Agent se ve la secuencia completa: el WebSocket devuelve 401 mientras el sistema no está agregado en el Hub, el Agent levanta el servidor SSH en el socket como respaldo y, al pulsar Agregar sistema, conecta por WebSocket y lo detiene:
WARN WebSocket connection failed err="unexpected status code: 401"
INFO Starting SSH server addr=/beszel_socket/beszel.sock network=unix
WARN WebSocket connection failed err="unexpected status code: 401"
INFO WebSocket connected host=172.17.0.1:8090
INFO Stopping SSH server
Agent en el Intel NUC
El NUC se conecta de forma saliente al Hub, así que no hace falta abrir ningún puerto en casa.
Este Agent no usa el Cloudflare Tunnel del NUC. El túnel sirve para el tráfico que entra hacia mis servicios en casa, y aquí la conexión va en sentido contrario: el Agent sale hacia el Hub por HTTPS.
1. Generar la configuración desde el Hub
En el panel, pulsa Agregar sistema otra vez. Beszel genera un docker-compose.yml ya preparado con la KEY, el TOKEN y la HUB_URL de tu Hub. Es la forma más cómoda de no equivocarse.
2. Crear el compose en el NUC
mkdir -p ~/Proyectos/beszel-agent
cd ~/Proyectos/beszel-agent
vim docker-compose.yml
Con :set paste activado en vim para no romper la indentación del YAML. Este es el contenido que generó Beszel:
services:
beszel-agent:
image: henrygd/beszel-agent
container_name: beszel-agent
restart: unless-stopped
network_mode: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./beszel_agent_data:/var/lib/beszel-agent
# monitor other disks / partitions by mounting a folder in /extra-filesystems
# - /mnt/disk/.beszel:/extra-filesystems/sda1:ro
environment:
LISTEN: 45876
KEY: "<KEY>"
TOKEN: "<TOKEN>"
HUB_URL: https://beszel.tudominio.com
La clave está en HUB_URL: apunta a la URL pública del Hub, por lo que el Agent abre una conexión WebSocket saliente atravesando Cloudflare y Nginx Proxy Manager.
3. Levantarlo y comprobar
docker compose up -d
docker logs beszel-agent --tail 50
Los logs mostraron lo que buscaba: el Agent detectó el disco y la interfaz de red, y la línea clave:
INFO WebSocket connected host=beszel.tudominio.com
Con eso, el NUC ya reportaba al Hub sin abrir puertos.
Resultado
El panel muestra las dos máquinas con CPU, memoria, disco, carga media, red y, en el NUC, la temperatura (la VM de Oracle no expone sensores):

Y la vista de contenedores reúne los de ambos sistemas en una sola tabla:

Desde el panel puedes entrar a cada sistema y cada contenedor para ver las gráficas históricas.
Seguridad
Montar el socket de Docker merece una explicación aparte.
:ro en el socket de Docker no es aislamiento
La configuración oficial de Beszel monta el socket de Docker con :ro (solo lectura), y es fácil pensar que eso hace el acceso inofensivo. No es así. La opción :ro solo afecta al archivo del socket en el sistema de archivos del contenedor. Un proceso que pueda conectarse al socket sigue pudiendo enviar peticiones a la API de Docker, incluidas las que crean o modifican contenedores, porque la API no distingue entre peticiones de lectura y de escritura según cómo se montó el socket.
En la práctica, quien controla el Agent tiene un acceso muy amplio al host. Mantengo el montaje porque es el que usa la configuración oficial y lo necesito para ver los contenedores, pero lo trato como un riesgo conocido. Si tu modelo de amenaza lo exige, existen proxies de socket que filtran qué llamadas a la API se permiten. Evaluar uno es parte de mis próximos pasos.
Que el Hub no quede expuesto
Dos decisiones reducen la superficie expuesta:
- El puerto
8090del Hub está publicado solo en172.17.0.1, no en la IP pública. - El Agent local escucha en un socket Unix y no en TCP, así que
45876no se abre en la VM.
Comprobaciones que conviene hacer en la VM y desde fuera:
# En la VM: 8090 debe escuchar solo en 172.17.0.1 y 45876 no debe aparecer
ss -tlnp | grep -E '8090|45876'
# Desde otra red (por ejemplo, tu equipo): debe fallar o agotar el tiempo
nc -zv -w 5 <IP_PÚBLICA_DE_LA_VM> 8090
Revisa también las reglas de red de tu proveedor (en Oracle Cloud, la Security List o los Network Security Groups) para confirmar que 8090 y 45876 no están abiertos al exterior.
Aprendizajes
Estos fueron los puntos en los que tuve que ajustar algo o que no eran evidentes:
- Crear el archivo en la carpeta correcta. Hice
mkdirpero nocd, yvim docker-compose.ymlcreó el archivo en el directorio padre. Parece trivial, pero es un clásico. - Orden de instalación. Hay que levantar primero el Hub, agregar el sistema en la interfaz para obtener la
KEYy elTOKEN, y solo entonces arrancar el Agent. 172.17.0.1en lugar delocalhost. Mi Nginx Proxy Manager llega a los servicios a través de la gateway de Docker. Por eso publiqué el puerto del Hub en esa dirección y no en127.0.0.1, que NPM no vería desde su contenedor.- Websockets Support en NPM. Sin esa opción, los agentes con
HUB_URLno conectan. APP_URLcon tu dominio. No dejeshttp://localhost:8090en una instalación publicada.- SELinux en
Enforcing. Agregué:za los volúmenes del Hub y del Agent local por precaución. No lo probé sin esa opción, así que no puedo asegurar que fuera necesaria. Si tu Agent dapermission deniedsobre el socket de Docker, revisagetenforceyls -Z /var/run/docker.sockantes de tocar políticas. - El puerto 45876 no siempre importa. Con
HUB_URL(WebSocket), no necesitas abrirlo en la máquina remota. Solo es relevante en el modo SSH, donde el Hub inicia la conexión.
Próximos pasos
Esto lo dejo para un próximo artículo:
- Alertas y notificaciones: umbrales de CPU, memoria, disco y estado de los sistemas, con un canal de notificación.
- Monitorear el RAID del NUC: montar una carpeta del volumen dentro de
/extra-filesystemspara ver su espacio. - Respaldo del Hub: usar la función de backups integrada o incluir
beszel_dataen mi rutina de respaldo. - Endurecer el acceso al socket de Docker: evaluar un proxy de socket que limite las llamadas permitidas. La propia documentación de Beszel lo sugiere mediante la variable
DOCKER_HOST. - Proteger el acceso al panel: hoy el login del Hub es público. Opciones: MFA por correo (
MFA_OTP, requiere SMTP), OAuth, o una regla de WAF / Cloudflare Access que deje libre/api/beszel/agent-connectpara que el NUC siga conectando. - Revisar la vista de detalles de contenedores: el panel permite ver el inspect y los logs de cada contenedor (variable
CONTAINER_DETAILS, activa por defecto). Quiero comprobar qué expone y decidir si la desactivo.
Conclusión
Para un laboratorio pequeño, Beszel resuelve lo que yo necesitaba con muy pocas piezas: un Hub, un Agent por máquina y un panel con histórico. La instalación fue directa y lo único que requirió atención fue la integración con mi proxy inverso y la conexión del NUC.
Prometheus + Grafana siguen siendo la opción cuando necesitas métricas propias de aplicaciones, consultas avanzadas o logs y trazas. Pero si lo que quieres es ver cómo están tus servidores y contenedores sin construir una plataforma de observabilidad, Beszel es un buen punto de partida.
Referencias
- Beszel. ¿Qué es Beszel?
- Beszel. Instalación del Hub
- Beszel. Instalación del Agent
- Beszel. Proxy inverso
- Beszel. Variables de entorno
- Prometheus. Overview
- Grafana. About Grafana
- knrdl. docker-socket-protector (explica por qué
:rono protege la API del socket) - Netdata. Docker socket security
