Convertí mi homelab en producción: observabilidad + un agente que la lee

Métricas, logs, alertas y un agente de IA que consulta mi infra en vivo — sobre un mini-PC, con contenedores least-privilege y secrets cifrados. La receta, y las decisiones honestas que la mayoría no cuenta.


“Tengo Docker y unas VMs” no es operar sistemas. Lo que separa un homelab de un entorno de producción no es cuántos servicios corres — es si sabes qué está pasando adentro y reaccionas cuando algo se rompe.

Así que le metí a mi homelab la misma disciplina que se le mete a producción: observabilidad de verdad, alertas que llegan a un humano, secrets cifrados, contenedores con el mínimo privilegio, y un agente de IA que lee toda esa telemetría. Todo en un mini-PC bajo el escritorio.

Esta es la receta — y, más importante, los trade-offs que tomé, incluyendo uno que dejé sin resolver a propósito.

El plano

Dos subsistemas. Arriba, cómo entra un request. Abajo, cómo el sistema se observa a sí mismo.

Internet → Cloudflare Tunnel (+ Zero-Trust Access) → nginx → servicios

  node-exporter ┐                                            │ sirve
  cAdvisor ─────┼──► Prometheus ──► Alertmanager ──► Discord  ▼
  Promtail ─────────► Loki                            AIOps agent
                        ▲   ▲                          (read-only)
                        └───┴──── PromQL / LogQL ───────┘

Números: 19 contenedores, 0 puertos expuestos al internet, 0 contenedores de aplicación privilegiados.

Observabilidad, no “un dashboard bonito”

El stack completo es un Compose aislado. Lo importante es que las métricas del host salen del node-exporter (la fuente correcta), no de un script con privilegios:

node-exporter:
  image: prom/node-exporter:v1.8.2
  command:
    - '--path.procfs=/host/proc'
    - '--path.rootfs=/host/root'
  volumes:
    - /proc:/host/proc:ro
    - /:/host/root:ro,rslave
  security_opt: [ "no-new-privileges:true" ]

Prometheus fija 4 reglas de alerta que importan: target caído, RAM > 90%, CPU > 90%, disco < 15%. Cuando una se sostiene, Alertmanager la manda a Discord. El truco para Discord: acepta payloads estilo Slack si le agregas /slack al webhook.

Seguridad primero, de verdad

Aquí es donde la mayoría de homelabs “impresionantes” hacen trampa. Yo tenía un servicio corriendo privileged: true con network_mode: host — acceso total al kernel, irónicamente el contenedor más peligroso. Lo bajé a lo mínimo:

security-backend:
  cap_drop: [ ALL ]
  cap_add:  [ DAC_READ_SEARCH ]   # solo para leer un log
  security_opt: [ "no-new-privileges:true" ]

De todas las capabilities a una de solo-lectura. El costo: perdí la visibilidad de red/puertos del host desde ese contenedor — que ahora vive en node-exporter, donde debe estar.

Los ~40 secrets pasaron de un .env en texto plano a SOPS + age: cifrados en reposo, la llave privada puede vivir fuera de la caja, y el deploy desencripta al vuelo. Soy honesto sobre esto: en un solo host la llave y el ciphertext conviven, así que el beneficio runtime es modesto — la ganancia real es cifrado-en-reposo y disaster recovery. Prefiero decir eso a venderlo como algo que no es.

El agente que lee la infra

La parte divertida. Le agregué a mi agente (GPT-OSS 120B vía Groq) tres herramientas read-only:

Ahora le pregunto en español y contesta con datos reales:

yo: ¿cómo está el CPU del host? agente: El CPU del host está en 2% y la RAM en 30%.

yo: muéstrame errores de la última hora agente: 11 registros de error, la mayoría de conexión en el componente querier…

Es read-only por diseño (está expuesto al internet público) y tiene budget de tokens y de dólares por día. Un prompt no puede mutar infra, y no me va a vaciar la cuenta.

Un detalle real: el modelo en Groq rompía el function-calling cuando las descripciones de las tools eran largas. La solución no fue pelearme con el modelo — fue acortar los schemas y mover el cheatsheet de PromQL al system prompt. Descripciones cortas, llamadas confiables.

La decisión que dejé sin resolver (a propósito)

cAdvisor no registra métricas por-contenedor en este host. La causa: mi Docker usa el storage driver overlayfs (el nuevo, de containerd), que cAdvisor v0.49 no sabe mapear a nombres de contenedor. Probé privileged: trueno ayuda, porque no es un problema de permisos.

Podría haber metido privileged para “que se vea que funciona”. No lo hice: sería un downgrade de seguridad sin beneficio real. Lo documenté como limitación conocida y seguí. Las métricas del host y los logs cubren las preguntas que de verdad hago.

Esa es, para mí, la diferencia entre un lab y operar sistemas: nombrar el failure mode y elegir el trade-off en vez de taparlo.

Lo que demuestra

No es “tengo Docker y unas VMs”. Es: construí y opero una infraestructura self-hosted con observabilidad, automatización, seguridad least-privilege y un agente de IA capaz de analizar el estado de los sistemas — y puedo explicar cada decisión.

El case-study visual completo, con diagrama de arquitectura, está en lalu.dev/homelab.


← todos los posts