| name | devops-operations |
| description | Patrones operativos de DevOps: deploy en NaN.builders (502, cache, env vars), cron jobs con scripts (no_agent=True), pipeline de digest estático, y GitHub Pages deployment. Todo para mantener apps en producción. |
| version | 1.2.0 |
| author | Hermes Agent |
| tags | ["devops","nan","deploy","cron","scripts","automation","production","github-pages"] |
DevOps Operations — Patrones de Producción
Patrones operativos para mantener aplicaciones en producción.
Tabla de Contenidos
- Código → NaN: Push y Verificación — edit → commit → pull --rebase → push → verify
- NaN Deploy Troubleshooting — 502, cache, TDZ, OOM
- Cron Jobs con Scripts — no_agent=True, Python scripts
- Static Digest Pipeline — Fetch API → scoring → JSON → HTML → Pages
- GitHub Pages + Vite — base path, crossorigin, deploys
0. Código → NaN: Flujo de Push y Verificación
⚠️ Regla de oro: Después de modificar código de un app desplegada en NaN, el trabajo NO está completo hasta que el push y la verificación en producción están hechos.
El flujo completo para cualquier cambio de código en apps NaN:
[1] Hacer cambios en local (dashboard.html, server.js, etc.)
[2] Verificar que funcionan localmente (curl localhost, revisar sintaxis)
[3] git add -A && git commit -m "fix: descripción clara"
[4] git pull --rebase origin main ← CRÍTICO: la app auto-commitea database.json vía syncGitHub
[5] git push origin main
[6] Esperar ~2 min a que NaN detecte el push y redeployee
[7] Verificar en producción: curl -s https://<app>.apps.nan.builders/healthz
[8] Verificar el cambio específico: curl endpoint, grep en HTML, etc.
Pitfall: git push rechazado por cambios remotos
- La app tiene
syncGitHub() que auto-commitea data/database.json tras cada mutación
- El repo local se queda detrás del remoto
- Siempre hacer
git pull --rebase antes de push (nunca merge — mantiene historia limpia)
- Síntoma:
! [rejected] main -> main (fetch first)
Pitfall: No verificar en producción
Pitfall: Asumir que el HTML se actualiza automáticamente
- NaN reconstruye el contenedor completo con Kaniko
- Los archivos estáticos (HTML, CSS, JS) están dentro de la imagen Docker
- No hay hot-reload — cada cambio requiere nuevo build
- Tiempo típico build+deploy: 1-5 min
🔥 Health check que miente: key existe pero no funciona
Patrón común: El endpoint /healthz verifica que la variable de entorno ORS_API_KEY exista en .env (string truthy check), pero NO hace una llamada real a la API para verificar que la key sea válida.
Síntoma: curl /healthz devuelve {"ors_api": true} pero las llamadas reales a la API retornan 403 Access disallowed o 401 Unauthorized.
Fix: El healthcheck debe hacer una llamada real (o al menos validar el formato de la key) además de verificar que exista:
checks.ors_api = !!process.env.ORS_API_KEY;
const testResp = await fetch('https://api.openrouteservice.org/v2/isochrones/driving-car', {
method: 'POST',
headers: { 'Authorization': process.env.ORS_API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ locations: [[0,0]], range: [1] })
});
checks.ors_api = testResp.ok;
Pitfall: Hacer la llamada real en cada request de healthcheck es lento (200-500ms). Mejor cachear el resultado y re-validar cada 5 minutos.
1. NaN Deploy Troubleshooting
TDZ (Temporal Dead Zone) — #1 causa de 502:
const/let usada antes de declaración → crash silencioso → Cloudflare 502
- Prevention: ordenar todas las
const al inicio de la función
NaN cachea contenedor Docker:
- Después de cambios JS/CSS, NaN puede servir versión antigua durante horas
- Soluciones: cambiar Dockerfile → renombrar repo → eliminar/recrear espacio
Scripts de verificación:
verify-nan-deploy.sh <base-url> — compara hashes MD5 locales vs remotos
git commit --allow-empty -m "chore: trigger redeploy" && git push — trigger de redeploy
Server crash silencioso:
- Container "Running" pero endpoints devuelven 502 con ~3-4s
- Fix:
process.on('uncaughtException') + fallback en route handlers
🔥 Browser tool cache agresivo — HTML nuevo no se sirve
Error real (2026-06-11): Tras hacer commit+push y redeploy en NaN, el browser tool seguía sirviendo HTML con JS antiguo. typeof THREE === 'undefined' aunque el CDN estaba en el HTML nuevo. El browser tool cachea el HTML y los scripts inline agresivamente.
Síntoma: curl desde terminal muestra HTML nuevo, pero browser_console(expression='typeof THREE') devuelve undefined. Los scripts CDN aparecen en el HTML pero no se ejecutan.
Soluciones (en orden de efectividad):
- Forzar redeploy con un commit mínimo en
database.json (o cualquier archivo servido por el server) → invalida cache de NaN
- Navegar con timestamp:
browser_navigate(url + '?t=' + Date.now()) — fuerza recarga del HTML
- Esperar 2-3 minutos — el cache de Cloudflare/NaN se expira
- Verificar con curl antes de confiar en el browser tool:
curl -s https://app.apps.nan.builders/ | grep 'three.min.js'
Regla: Si el HTML sirve correctamente (verificado con curl) pero el browser tool muestra comportamiento antiguo → es cache. No buscar bugs donde no los hay.
Puerto del Dockerfile ≠ Container port de NaN — causa común de 502:
- NaN tiene un campo Container port en la config del espacio (Settings > Container port)
- Si el servidor escucha en otro puerto (ej. 3000) pero NaN espera 7070 → 502 inmediato
- Fix: sincronizar ambos. Opción A: cambiar
ENV PORT=7070 + EXPOSE 7070 en Dockerfile. Opción B: cambiar Container port en NaN a 3000
- Recomendado: Opción A (Dockerfile), así el build es autónomo y no depende de config manual de NaN
- Verificar:
curl -s -o /dev/null -w "%{http_code}" https://<app>.apps.nan.builders/ debe dar 200 tras el build
Dockerfile faltante — error silencioso de Kaniko:
NaN build succeeded pero deployment stuck en "pending":
- A veces el build de Kaniko termina con éxito (imagen creada en registry) pero NaN no despliega el contenedor y se queda en estado "pending" indefinidamente.
- Síntoma: build history muestra "succeeded" con imagen, pero la URL pública da 404 y el status de la app es "pending".
- Causa probable: NaN no asigna recursos al contenedor (problema de orquestación interna) o el webhook de deploy no se dispara tras el build.
- Fixes:
- Desde la UI de NaN, darle a "Deploy" o "Restart" manualmente
- Cambiar el Container port en Settings y hacer deploy de nuevo (fuerza re-asignación)
- Cambiar el puerto en el código (
server.js + Dockerfile), pushear, y esperar nuevo build+deploy
- Si nada funciona, borrar la app y crearla de nuevo desde cero
- Prevención: no hay forma segura de evitarlo — es un problema de la plataforma NaN, no del código.
Dashboard dual: local backend + NaN frontend:
- El dashboard de control (monitorización del sistema) tiene dos caras:
- Local (microVM, puerto 4040): backend con datos reales del sistema (CPU, RAM, procesos, ChromaDB, crons). Usa
execSync, os, fs para datos en vivo.
- NaN (contenedor): versión visual que consume APIs del local. Como el contenedor no ve el sistema real, los endpoints deben tener fallbacks graceful (try/catch con datos de ejemplo).
- Flujo de creación:
- Desarrollar y testear localmente primero (el microVM tiene todos los datos reales)
- Crear Dockerfile y subir a GitHub
- Conectar repo a NaN como app
- El contenedor de NaN no tiene acceso a ChromaDB local → el endpoint
/api/skills debe devolver { status: 'disconnected' } gracefulmente
- El contenedor de NaN no ve procesos del host →
/api/processes debe tener fallback
- Puerto: elegir uno que no choque con otras apps. El puerto 4000 está ocupado por el ESIOS Dashboard (u otro proyecto de David). Usar 4040 o 6060 para nuevos dashboards.
- Auth: Basic Auth con contraseña vía env var
DASH_PASSWORD
- Auto-refresh: frontend con
setInterval(fetch, 5000) para datos en vivo
- Repo privado:
github.com/Ntizar/Mastermind-Dashboard
- Flujo de creación de dashboard desde cero:
- Crear repo privado en GitHub via API REST (
curl -X POST -H "Authorization: token $GITHUB_TOKEN" ...)
- Inicializar git local, hacer primer commit, pushear
- Desarrollar backend (Express) y frontend (HTML+CSS+JS) localmente
- Testear en localhost con datos reales del microVM
- Crear Dockerfile y entrypoint.sh
- Pushear todo → NaN detecta el push y construye automáticamente
- Cuidado: el primer push NO debe incluir
node_modules/ — añadir .gitignore antes del primer commit o limpiar con git rm -r --cached node_modules después
- Cuidado: el Dockerfile debe estar en el repo ANTES de que NaN intente construir, o el build fallará con "Dockerfile not found"
Infinite recursion → OOM:
- 4GB RAM hard limit, sin swap
- Fix: reemplazar recursión con fetch único
2. Cron Jobs con Scripts
⚠️ cronjob tool no disponible en esta VM:
- El
cronjob tool no existe en el entorno actual — no hay crontab, no hay daemon cron, no hay systemd timers
- Los scripts de mantenimiento se guardan en
/hermes-home/scripts/ y se ejecutan manualmente o desde un cron externo (SSH desde otra máquina)
- Para automatizar: configurar cron en máquina local que SSH al VM, o usar systemd timer en el VM
- Ejemplo: script
/hermes-home/scripts/mastermind-weekly-maintenance.sh (Domingo 05:00 UTC)
Patrón de scripts de mantenimiento:
- Script en
/hermes-home/scripts/ con shebang #!/bin/bash
- Script usa
set -e y loguea a /var/log/<name>.log
- Script incluye health checks antes y después de cada paso
- Script hace
git add -A && git commit && git push al final
Pitfalls:
- Script path: SOLO nombre de archivo (el scheduler añade el prefix)
- Schedule usa UTC
- Scripts ejecutan en sesión aislada → no tienen contexto de chat
- Scripts deben incluir retry logic para APIs externas (3 intentos, 2s delay)
- No usar
cronjob tool — no existe en esta VM. Crear scripts bash ejecutables manualmente.
3. Static Digest Pipeline
Pipeline para feeds periódicos:
- Fetch API externa
- Normalización y scoring heurístico
- Generar JSON + HTML
- Deploy a GitHub Pages
Ver skill devops/static-digest-pipeline para la implementación completa.
4. Deploy Audit — Verificación de despliegues
Procedimiento sistemático para auditar y verificar despliegues en NaN.builders.
Pasos
- Descargar deploy remoto y comparar con local (
diff)
grep -c por elementos clave en ambos archivos
- Verificar git status y últimos commits
- Verificar endpoints secundarios
Checklist de integridad HTML
Pitfalls
- NaN bloquea curl desde ciertas IPs (403) → usar
curl -A "Mozilla"
- Tamaños iguales ≠ contenido idéntico → usar
diff
5. Error 502 por desajuste de puertos
NaN.builders tiene 3 lugares donde se define el puerto, y deben coincidir:
| Lugar | Dónde está | Ejemplo |
|---|
| Container Port | UI de NaN → Settings del espacio | 6060 |
EXPOSE | Dockerfile | EXPOSE 6060 |
process.env.PORT | server.js | `const PORT = process.env.PORT |
Diagnóstico rápido
grep EXPOSE Dockerfile
grep 'PORT' server.js
grep -A2 HEALTHCHECK Dockerfile
Fix recomendado
Alinear todo al Container Port de NaN. O escuchar en ambos puertos (tolerante a cambios).
Healthcheck: regla de oro
El HEALTHCHECK NUNCA debe apuntar a un endpoint protegido por auth. Crear /healthz ANTES del middleware de auth.
NaN containers están AISLADOS del host
No pueden ver procesos, crons, skills, sessions ni archivos del host. Sincronizar datos vía Git.
6. Aurora Nightly — Mejora Continua Nocturna
Pipeline de 4 jobs nocturnos para mejora continua del CSS Aurora:
- Job #1 (01:00): Investigación web de tendencias CSS
- Job #2 (02:00): Análisis gap + Mejora CSS #1
- Job #3 (03:00): Mejora CSS #2
- Job #4 (04:00): Mejora CSS #3 + Reaprendizaje
Ver aurora-nightly para el procedimiento completo con investigación RSS, patrones de mejora CSS, y pitfall de no reescribir packs enteros.
GitHub Actions Nightly Pipeline
Para proyectos con CI/CD: lint → build → test → deploy en cron nocturno. Ver aurora-nightly-pipeline para el workflow YAML completo.
7. GitHub Pages + Vite
Pitfalls comunes al desplegar proyectos Vite en GitHub Pages. Referencia completa: references/vite-github-pages-deploy.md.
Checklist rápida:
🔥 GitHub Pages NO funciona en repos privados con plan free
2026-06-18 (AtlasMadrid2024): POST /repos/{owner}/{repo}/pages devuelve 422: "Your current plan does not support GitHub Pages for this repository." Incluso tras crear el sitio de Pages vía API, el workflow de GitHub Actions falla en "Setup Pages" con el mismo error.
Fix: Cambiar el repo a público antes de activar Pages:
curl -X PATCH -H "Authorization: token $TOKEN" \
"https://api.github.com/repos/OWNER/REPO" \
-d '{"private":false}'
curl -X POST -H "Authorization: token $TOKEN" \
"https://api.github.com/repos/OWNER/REPO/pages" \
-d '{"build_type":"workflow","source":{"branch":"main","path":"/"}}'
Workflows que fallan sin Pages habilitado: El step "Setup Pages" (actions/configure-pages@v5) falla → "Upload artifact" y "Deploy" se saltan. No hay error claro de "Pages not enabled" — solo falla genérica.
Detección: Si el workflow falla en "Setup Pages" y el repo es privado → Pages no está soportado en este plan.
Alternativa si no se puede hacer público: Deploy manual a gh-pages branch con git subtree o deploy a otro hosting (NaN.builders, Vercel, Netlify).
Patrón de deploy manual (cuando no hay GitHub Actions):
npm run build
sed -i 's/ crossorigin//g' dist/index.html
git init /tmp/gh-deploy && cp -r dist/* /tmp/gh-deploy/