Dashboard como imagen en la red local

http://192.168.117.7:9100/latest.png sirve una captura del dashboard Compras, actualizada sola cada hora, sin login — para un equipo en la red local que necesita verlo sin credenciales de Lightdash (ej. una pantalla fija o un carrusel). No pasa por dash.frento.com.mx ni por el túnel de Cloudflare: es una URL aparte, solo accesible dentro de la red 192.168.117.0/24.

Por qué no es simplemente “compartir el dashboard”

Lightdash corre en la edición gratuita/self-hosted, sin license key. Tanto el “shareable link” como el “embedding” completo son en realidad la misma función (motor JWT compartido) y ambos están gateados a Cloud/Enterprise — confirmado contra la propia instancia: el controlador vive en packages/backend/dist/ee/controllers/embedController.js (carpeta ee/, igual que la pantalla “Welcome” que también resultó ser Enterprise) y /api/v1/health devuelve embedding.enabled: false. No hay manera de exponer un dashboard sin autenticación usando la función nativa de Lightdash en esta instancia.

La alternativa real dentro de la edición gratuita: un scheduled delivery en formato imagen. Lightdash sí trae esa función completa (requiere un headless browser, que esta instancia no tenía — ver Lightdash § Arquitectura para el detalle de ghcr.io/browserless/chromium:v2.49.0 agregado al docker-compose.yml) — genera un PNG del dashboard y lo sube al bucket S3/MinIO interno como parte normal de su flujo de entrega. Ese PNG es justo lo que este pipeline intercepta y republica sin autenticación.

Cómo funciona, de punta a punta

cron (Lightdash, 0 * * * *)
  -> scheduled delivery "Compras - export horario a Minio" (formato image)
  -> headless browser renderiza el dashboard, sube PNG a MinIO (bucket "lightdash")
  -> MinIO dispara webhook (bucket notification, filtro *.png)
  -> receiver.py (systemd: lightdash-dashboard-pipeline.service, puerto 9100)
     copia el objeto más reciente a /var/www/dashboard/latest.png
  -> mismo receiver.py sirve ese archivo por HTTP en /latest.png

Nada de este flujo necesita intervención manual una vez configurado — se verificó end-to-end dos veces (una simulando el webhook a mano, otra dejando correr el pipeline real completo) y en ambas el archivo se actualizó solo.

Piezas nuevas

  • Scheduled delivery: dashboard Compras, schedulerUuid 17fb027e-d2b4-4e68-8600-28931afc6b3d, cron 0 * * * *, format: image. Target: un email desechable (dashboards@ctunlinux.local, nunca se envía de verdad) — Lightdash exige al menos un destino para guardar el scheduler aunque el destino real (el bucket) no sea configurable como tal. Ver sources/trivasa/lightdash-dashboards-demo/pipeline-imagen-webhook.md para por qué no existe un target “S3 directo”.
  • ghcr.io/browserless/chromium:v2.49.0 (contenedor lightdash-headless-browser) — agregado al docker-compose.yml de Lightdash. 3.84GB, el pull falló dos veces antes por espacio en disco del host (ver ctunlinux para el contexto de RAM/disco ajustados) — completó a la tercera con más margen.
  • Notificación de bucket en MinIO: notify_webhook:dashboard -> http://172.21.0.1:9100/minio-event (172.21.0.1 es el gateway de la red Docker lightdash_default, la forma en que un contenedor de esa red llega al host), filtro suffix=.png.
  • lightdash-dashboard-pipeline.service (systemd, enabled+active) — corre scripts/lightdash/receiver.py, un solo script de Python (librería estándar, sin dependencias) que hace de webhook receiver y de servidor HTTP a la vez. No usa credenciales S3 propias: trae el objeto vía docker exec lightdash-minio mc cat ... (MinIO ya trae mc empacado en su propia imagen). Deploy: copia idéntica del script en trivasa-bi-dev/lightdash/dashboard-pipeline/receiver.py en ctunlinux — si se edita, actualizar los dos (repo primero, después el host
    • systemctl restart lightdash-dashboard-pipeline.service).

Gotcha resuelto: el modal de onboarding salía en las capturas

La primera captura de prueba salió con un modal de Lightdash tapando la mitad del dashboard (“Nearly there… Tell us a bit more about yourself”) — el usuario admin de esta instancia se creó por API (ver Lightdash § Acceso) y nunca pasó por el flujo de onboarding normal de la UI, así que isSetupComplete seguía en false. Se completó vía PATCH /api/v1/user/me/complete (el mismo endpoint que había fallado semanas atrás por no existir todavía la organización — ahora sí funciona) y las capturas siguientes salieron limpias.

Cambiar el dashboard o la frecuencia

Editar el scheduler existente desde la UI de Lightdash (Compras → ⚙ → Scheduled deliveries), o crear uno nuevo por API contra otro dashboard — sources/trivasa/lightdash-dashboards-demo/pipeline-imagen-webhook.md trae el body exacto de la llamada POST /api/v1/dashboards/{uuid}/schedulers. El receiver no necesita ningún cambio: sirve lo último que caiga en el bucket lightdash con sufijo .png, sin importar qué scheduler lo generó — si se agrega un segundo scheduler para otro dashboard, ambos escriben al mismo /var/www/dashboard/latest.png (el que llegue después pisa al anterior). Un carrusel real con más de un dashboard necesitaría rutas de salida separadas por scheduler — implementado en Carrusel de dashboards para TV, que reemplazó el scheduler original de Compras por 3 (uno por dashboard). Este servicio (lightdash-dashboard-pipeline.service, puerto 9100) sigue activo tal cual, pero como resultado ya no muestra siempre Compras — sirve el último .png subido al bucket sin importar cuál de los 3 schedulers lo generó.

Véase también

  • Lightdash — la instancia y los dashboards que este pipeline expone.
  • Carrusel de dashboards para TV — extensión de este mismo patrón a los 3 dashboards en rotación, sirviendo en el puerto 8888.
  • ctunlinux — RAM/disco del host, relevante para el peso del headless browser.
  • sources/trivasa/lightdash-dashboards-demo/pipeline-imagen-webhook.md — cierre técnico completo: schema exacto de la API de schedulers, formato del evento de MinIO, y el script receiver.py completo.