Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

VPS Control Panel

Panel de control centralizado para un VPS pequeño con varios proyectos en Docker. En una sola pantalla de Grafana se ve cómo está el servidor (CPU, RAM, disco), cuánto consume cada contenedor y si cada servicio responde, gracias a Uptime Kuma. Todo se levanta con un docker compose up -d, detrás de Nginx y con HTTPS.

Hoy vigila mi VPS de Contabo (roviradev.duckdns.org), que aloja:

  • mosca-borracha: una simulación del cerebro de una Drosophila bajo los efectos del alcohol, con su consola web en tiempo real (FastAPI + WebSocket).
  • Jeffrey: un bot de WhatsApp (Baileys + Kimi) que gestiona mi horario universitario, con transcripción de notas de voz local (faster-whisper).

English summary. A self-hosted monitoring stack for a small multi-project VPS: Grafana (single pane of glass), Prometheus + node-exporter (host CPU/RAM/disk), cAdvisor (per-container usage) and Uptime Kuma (is each service up?), behind Nginx on its own subdomain. Caddy on the host terminates TLS. Nothing listens on a public interface, internal networks have no Internet egress, the Docker socket is only exposed read-only through a filtering proxy, and dashboards and data sources are provisioned as code.

Servidor: CPU, RAM, disco y carga

Contenedores: CPU, memoria y red de cada servicio


Índice

  1. Qué se ve en el panel
  2. Arquitectura
  3. Stack
  4. Decisiones de diseño
  5. Instalación
  6. Uptime Kuma
  7. Operación
  8. Estructura del repositorio

Qué se ve en el panel

El dashboard VPS · roviradev es la página de inicio de Grafana y está en la carpeta VPS. Se provisiona desde grafana/dashboards/vps-overview.json, así que no hay que montarlo a mano.

Sección Paneles Fuente
Servidor tiempo encendido, CPU, RAM, disco, carga por núcleo, contenedores activos; CPU por tipo de uso (user, system, iowait, steal), memoria (en uso + caché bajo el total), ocupación de cada sistema de ficheros, lectura/escritura en disco node-exporter
Servicios estado de cada servicio a lo largo del tiempo (arriba / caído / pendiente / mantenimiento), estado actual, tiempo de respuesta, días que le quedan a cada certificado HTTPS Uptime Kuma
Contenedores CPU, memoria y tráfico de red de cada contenedor (mosca, bot, voz y el propio panel) cAdvisor

Uptime Kuma tiene además su propia interfaz en status.<dominio>: ahí se configuran los monitores y las notificaciones (Telegram, correo, etc.).

Arquitectura

flowchart TB
    user(["Navegador"]) -->|"HTTPS :443"| caddy

    subgraph vps["VPS · Ubuntu 24.04 · UFW: solo 22, 80 y 443"]
        caddy["Caddy (host)<br/>TLS con Let's Encrypt"]
        caddy -->|"roviradev.duckdns.org"| mosca["mosca<br/>127.0.0.1:8000"]
        caddy -->|"panel.* y status.*<br/>127.0.0.1:8080"| nginx

        subgraph panel["docker compose -p panel"]
            nginx["Nginx"]
            grafana["Grafana"]
            kuma["Uptime Kuma"]
            prometheus["Prometheus"]
            node["node-exporter"]
            cadvisor["cAdvisor"]
            proxy["docker-socket-proxy<br/>solo lectura"]
        end

        bot["horario-bot + horario-stt<br/>(red horario-bot_default)"]
    end

    nginx -->|"panel.*"| grafana
    nginx -->|"status.*"| kuma
    grafana -->|PromQL| prometheus
    prometheus -->|scrape| node
    prometheus -->|scrape| cadvisor
    prometheus -->|"scrape /metrics"| kuma
    kuma -->|"¿contenedor en marcha?"| proxy
    kuma -.->|"GET /health"| bot
    kuma -.->|"HTTPS a las URLs públicas"| caddy
Loading

Recorrido de una petición. El navegador pide https://panel.roviradev.duckdns.org. Caddy, que ya servía la mosca, pone el certificado y le pasa la petición a Nginx en 127.0.0.1:8080. Nginx mira el Host y la reparte:

Subdominio Destino Notas
panel.<dominio> Grafana login propio, límite de intentos en /login, WebSocket para Grafana Live
status.<dominio> Uptime Kuma la interfaz va por socket.io (WebSocket); /metrics bloqueado desde fuera
cualquier otro Host nada Nginx cierra la conexión sin responder (444)

Redes Docker. Cada servicio está solo en las redes que necesita:

Red Tipo Quién está Para qué
edge bridge, subred fija nginx, grafana, uptime-kuma tráfico web; Kuma sale a Internet desde aquí para comprobar las URLs públicas
monitoring internal (sin salida a Internet) prometheus, node-exporter, cadvisor, grafana, uptime-kuma recogida de métricas
docker-api internal docker-socket-proxy, uptime-kuma consultar el estado de los contenedores
horario-bot_default externa (del bot) uptime-kuma comprobar el healthcheck interno del servicio de voz, que no publica puertos

Stack

Componente Versión Función
Caddy la del host HTTPS automático (Let's Encrypt) y proxy hacia Nginx
Nginx 1.30.5 (alpine) reverse proxy por subdominio, cabeceras de seguridad, límite de login
Grafana 13.2.2 panel visual único, provisionado como código
Prometheus 3.14.0 base de datos de métricas (30 días o 5 GB, lo que llegue antes)
node-exporter 1.12.1 métricas del servidor
cAdvisor 0.60.6 métricas por contenedor
Uptime Kuma 2.5.5 ¿responde cada servicio?, historial, notificaciones
docker-socket-proxy 0.5.0 acceso de solo lectura a la API de Docker para Uptime Kuma
Docker Compose v5 orquestación

Todas las imágenes van con la versión fijada, así que una actualización nunca llega por sorpresa.

Decisiones de diseño

Caddy delante, Nginx detrás. El VPS ya tenía Caddy en los puertos 80 y 443 sirviendo la mosca con HTTPS automático. En vez de migrarlo todo a Nginx + certbot, que habría obligado a tocar un servicio que funciona (WebSocket, cabeceras de IP real...), Caddy solo pone el TLS y le pasa los subdominios del panel a Nginx. Nginx hace el enrutado y el endurecimiento. Así cada proyecto se despliega y se rompe por separado.

Nada escucha en una IP pública. Los únicos puertos publicados son 127.0.0.1:8080 (Nginx) y 127.0.0.1:3001 (Kuma, para entrar por túnel SSH). Esto importa porque Docker se salta UFW: un ports: "3000:3000" quedaría abierto a Internet aunque el firewall diga lo contrario.

Prometheus y los exporters no ven Internet. La red monitoring es internal: true: ni Prometheus ni node-exporter ni cAdvisor pueden hacer peticiones hacia fuera, y nada de fuera llega a ellos. La interfaz de Prometheus no se publica: para explorar métricas está Explore en Grafana.

El socket de Docker, filtrado. Montar /var/run/docker.sock en Uptime Kuma equivale a darle root en el host. En su lugar, Kuma habla con docker-socket-proxy, que solo permite GET /containers/...: parar un contenedor o ver las imágenes devuelve 403. Además, el proxy está en su propia red interna para que Grafana o Prometheus no puedan consultarlo, porque la API de contenedores expone las variables de entorno de los demás servicios. cAdvisor sí monta el socket y va en modo privileged: lo necesita para leer los cgroups y la red de cada contenedor. Es el componente con más privilegios del stack y por eso no tiene salida a Internet ni puertos publicados.

Uptime Kuma en su propio subdominio. Uptime Kuma no admite servirse bajo una subruta como panel.<dominio>/status, así que tiene el suyo. En DuckDNS cualquier subdominio de tu nombre ya resuelve a tu IP, sin configurar nada.

node-exporter sin métricas de red. Para ver el tráfico real del host, node-exporter tendría que correr con network_mode: host. Entonces Prometheus, dentro de una red Docker, no podría alcanzarlo sin abrir un puerto en UFW. Como el objetivo era CPU, RAM y disco, desactivo los colectores de red (mostrarían la interfaz del propio contenedor, que engaña) y el tráfico se mide por contenedor con cAdvisor.

Todo como código. La fuente de datos y el dashboard se provisionan desde ficheros. Grafana no deja guardar cambios encima (allowUiUpdates: false), así que el repo es siempre la verdad. La configuración de Nginx es una plantilla y los dominios salen de .env.

Secretos fuera del repo y fuera del entorno. La contraseña de admin de Grafana se lee de un fichero (GF_SECURITY_ADMIN_PASSWORD__FILE), no de una variable de entorno visible con docker inspect. La API key de Kuma para Prometheus va por password_file. Los dos están en secrets/, con permisos 400 y a nombre del usuario sin privilegios de cada contenedor.

Límites para convivir. Cada contenedor tiene mem_limit, cpus y no-new-privileges, y los logs rotan (3 × 10 MB). El panel entero usa unos 550 MB de RAM y apenas CPU en reposo, en un VPS donde el servicio de voz del bot puede llegar a 2 GB.

Grafana en español (GF_USERS_DEFAULT_LANGUAGE=es-ES) y sin llamadas a casa: sin analítica, sin comprobación de actualizaciones y sin feed de noticias.

Instalación

Requisitos

  • Un servidor Linux con Docker y Docker Compose v2+.
  • Caddy en el host, u otro proxy con TLS, que envíe los subdominios a 127.0.0.1:8080.
  • Dos subdominios que apunten al servidor: uno para Grafana y otro para Uptime Kuma.
  • Si no usas el bot de WhatsApp, quita la red bot de uptime-kuma en docker-compose.yml (y su definición abajo), porque Compose exige que las redes externas existan.

1. Código y configuración

git clone https://github.com/Alvaro-Rovira/vps-control-panel.git /opt/panel
cd /opt/panel
./scripts/init-secrets.sh      # genera la contraseña de Grafana y crea .env desde .env.example
nano .env                      # dominios, zona horaria, retención...

init-secrets.sh nunca sobrescribe un secreto que ya exista.

2. Arrancar

docker compose -p panel up -d
docker compose -p panel ps     # todo "Up"; nginx, cadvisor y uptime-kuma, "healthy"

Si prefieres desplegar desde tu ordenador, ./scripts/deploy.sh root@IP sube el proyecto con rsync (sin tocar .env ni secrets/) y hace el up -d.

3. HTTPS con Caddy

Añade a /etc/caddy/Caddyfile el bloque de caddy/Caddyfile.snippet con tus dominios y recarga:

caddy validate --config /etc/caddy/Caddyfile && systemctl reload caddy

Deja fuera status.<dominio> hasta completar el paso 1 de Uptime Kuma. La cuenta de administrador de Kuma se la queda quien entra primero, y un certificado nuevo aparece en los registros públicos de Certificate Transparency, que los bots vigilan para encontrar instalaciones sin configurar.

4. Entrar en Grafana

Usuario admin (o el de GRAFANA_ADMIN_USER). La contraseña está en el servidor:

cat /opt/panel/secrets/grafana_admin_password

Uptime Kuma

1. Crear la cuenta de administrador (por túnel SSH)

Kuma solo escucha en 127.0.0.1:3001 del servidor. Desde tu ordenador:

ssh -L 3001:127.0.0.1:3001 root@IP_DEL_VPS

Abre http://localhost:3001, crea el usuario administrador y, después, añade status.<dominio> a Caddy (paso 3 de la instalación).

2. Conectar Docker (solo lectura)

Settings → Docker Hosts → Setup Docker Host: tipo TCP / HTTP, dirección tcp://docker-socket-proxy:2375.

3. Monitores

Los que uso en este VPS:

Monitor Tipo Configuración
mosca · web HTTP(s) https://roviradev.duckdns.org/, códigos aceptados: 404. La consola exige un enlace con token y sin él responde 404 a todo. Si la app cae, Caddy devuelve 502, así que 404 significa «funciona».
mosca · contenedor Docker Container mosca
Jeffrey · bot Docker Container horario-bot (el bot no publica ningún puerto)
Jeffrey · voz HTTP(s) http://horario-stt:9000/health (por la red interna del bot)
Panel · Grafana HTTP(s) https://panel.roviradev.duckdns.org/api/health

4. Métricas en Grafana

Para que la sección Servicios del dashboard tenga datos, Prometheus necesita leer el /metrics de Kuma:

  1. En Kuma: Settings → API Keys → Add API Key. Copia la clave (empieza por uk).

  2. En el servidor, guárdala. El script la pide sin mostrarla en pantalla ni dejarla en el historial:

    /opt/panel/scripts/set-kuma-api-key.sh

En unos 30 segundos el target uptime-kuma pasa a up y el estado de cada servicio aparece en Grafana.

Operación

cd /opt/panel
docker compose -p panel ps                     # estado
docker compose -p panel logs -f grafana        # logs de un servicio
docker compose -p panel pull && docker compose -p panel up -d   # tras cambiar versiones en el compose
  • Cambiar el dashboard. Edita grafana/dashboards/vps-overview.json (o expórtalo como JSON desde Grafana) y despliega: Grafana lo recarga en menos de un minuto.

  • Copias de seguridad. Lo único con estado son los volúmenes panel_grafana-data, panel_prometheus-data y panel_kuma-data. Lo más valioso es el de Kuma, con los monitores y su historial:

    docker run --rm -v panel_kuma-data:/data -v "$PWD":/backup alpine tar czf /backup/kuma-$(date +%F).tgz -C /data .
  • Regenerar las capturas del README. RANGE=6h ./scripts/screenshots.sh levanta un Grafana temporal de solo lectura en el VPS (sin usar la cuenta de admin), hace las capturas con Playwright y lo borra todo al terminar.

  • Desinstalar. docker compose -p panel down (añade -v para borrar también los datos) y quita el bloque del panel del Caddyfile.

Estructura del repositorio

.
├── docker-compose.yml                  # los 7 servicios, redes, volúmenes y secretos
├── .env.example                        # dominios, zona horaria, retención, red del bot
├── nginx/
│   ├── templates/panel.conf.template   # vhosts de Grafana y Kuma (envsubst al arrancar)
│   └── default.conf                    # vacío: anula la página de bienvenida de la imagen
├── prometheus/prometheus.yml           # qué se recoge y cada cuánto
├── grafana/
│   ├── provisioning/datasources/       # Prometheus como fuente de datos
│   ├── provisioning/dashboards/        # dónde están los dashboards
│   └── dashboards/vps-overview.json    # el panel
├── caddy/Caddyfile.snippet             # lo que hay que añadir al Caddy del host
├── scripts/
│   ├── init-secrets.sh                 # contraseña de Grafana, .env (en el servidor)
│   ├── set-kuma-api-key.sh             # guarda la API key de Kuma (en el servidor)
│   ├── deploy.sh                       # rsync + compose up (desde tu ordenador)
│   └── screenshots.sh                  # capturas del README
├── secrets/                            # (ignorado por git) grafana_admin_password, kuma_api_key
└── docs/screenshots/

About

Panel de control para un VPS con varios proyectos: Grafana + Prometheus + node-exporter + cAdvisor + Uptime Kuma detrás de Nginx, con Docker Compose

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages