Skip to main content

esios-nan-deploy

Procedimiento de deploy de proyectos Node.js en NaN.builders — Dockerfile, Kaniko, variables de entorno, puertos y troubleshooting.

Zur Installation springen

Quellinformationen

Repository
Ntizar/NtizarBrainMasterMind
Letzte Quellaktivität
26. Juni 2026 um 12:05
Erkannte Sprache von SKILL.md
Spanisch
Sterne
2
Forks
0

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

Datei-Explorer
10 Dateien

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
esios-nan-deploy
description
Procedimiento de deploy de proyectos Node.js en NaN.builders — Dockerfile, Kaniko, variables de entorno, puertos y troubleshooting.
version
2.5.0
author
Ntizar
tags
["esios","deploy","nan","docker"]
related_skills
["nan-puerto-desajuste","nan-pending-root-cause"]
# Deploy en NaN.builders Guía para desplegar proyectos Node.js en la plataforma NaN.builders (microVMs KVM/QEMU). ## Estructura del deploy 1. **Push a GitHub** → NaN detecta el cambio y hace build con Kaniko 2. **Kaniko build** → construye la imagen Docker sin daemon 3. **Auto-deploy** → si el build funciona, se despliega automáticamente ### Patrón de archivos .env (CRÍTICO) Cada proyecto necesita estos 3 archivos: ``` proyecto/ ├── .env ← local, NO en Git (desarrollo) ├── .env.example ← SÍ en Git (documentación de variables) ├── .dockerignore ← excluye node_modules, .git (NO .env — se copia en contenedor) └── .gitignore ← excluye .env ``` **⚠️ Pitfall: `NAN_API` no se hereda en el contenedor de NaN** Las variables de entorno del host (`process.env.NAN_API` en la sesión de Hermes) **NO están disponibles dentro del contenedor Docker de NaN**. El contenedor solo tiene: 1. Variables configuradas en la pestaña **Env** del dashboard NaN 2. Variables copiadas desde archivos del proyecto (`.env` copiado por `COPY . .`) **Patrón de fallback para tokens API** (implementado en server.js de dieta): ```javascript function getNanToken() { // 1) process.env.NAN_API (NaN dashboard Env) if (process.env.NAN_API) return process.env.NAN_API; // 2) NTIZAR_API (otro nombre posible) if (process.env.NTIZAR_API) return process.env.NTIZAR_API; // 3) Leer .env del proyecto (fallback local) try { const envContent = fs.readFileSync(path.join(__dirname, '.env'), 'utf8'); const match = envContent.match(/^NAN_API=(.+)$/m); if (match) return match[1].trim(); } catch (e) {} return ''; } ``` **Configuración para deploy:** - Crear `.env` con el token real en el directorio del proyecto - Añadir `.env` a `.gitignore` (NUNCA subir a Git) - **NO** añadir `.env` a `.dockerignore` (se necesita en el contenedor) - El Dockerfile `COPY . .` lo copiará automáticamente **.env.example** — template con nombres y placeholders: ``` ESIOS_API_TOKEN=tu_token_aqui PORT=4000 NAN_API_KEY=tu_clave_aqui ``` **.env** — valores reales (solo local): ``` ESIOS_API_TOKEN=abc123... PORT=4000 ``` ### Dónde configurar variables en producción - **NaN:** pestaña **Env** en la web de NaN → dashboard del espacio - **NUNCA** en el código, commits, o .env en Git - Se acceden via `process.env.VAR_NAME` en Node.js ### Validación en código (3 patrones) **Patrón A — Exit early (recomendado para obligatorias):** ```javascript // src/config/env.js const REQUIRED = ['ESIOS_API_TOKEN']; function loadEnv() { const missing = REQUIRED.filter(k => !process.env[k]); if (missing.length > 0) { console.error(`Faltan: ${missing.join(', ')}`); process.exit(1); } return { ESIOS_TOKEN: process.env.ESIOS_API_TOKEN, ... }; } ``` **Patrón B — Health endpoint con checks:** ```javascript app.get('/readyz', (req, res) => { const esiosReady = Boolean(process.env.ESIOS_API_TOKEN); res.status(esiosReady ? 200 : 503).json({ status: esiosReady ? 'ready' : 'degraded', checks: { esios_api_token: esiosReady } }); }); ``` **Patrón C — Fallback con generación temporal:** ```javascript const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || crypto.randomBytes(24).toString('base64url'); // Temporal ``` ## Dockerfile mínimo para NaN ```dockerfile FROM node:20-alpine AS deps WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci --omit=dev FROM node:20-alpine AS runner WORKDIR /app ENV NODE_ENV=production ENV PORT=4000 RUN addgroup -S appgroup && adduser -S appuser -G appgroup COPY --from=deps --chown=appuser:appgroup /app/node_modules ./node_modules COPY package.json ./ COPY server.js ./ COPY src/ ./src/ COPY public/ ./public/ COPY data/ ./data/ USER appuser EXPOSE 4000 HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD wget -qO- http://localhost:4000/healthz || exit 1 CMD ["node", "server.js"] ``` ## 🚧 Primer deploy: crear el espacio en NaN Antes de que cualquier URL funcione, **hay que crear el espacio en la web de NaN**: 1. Ir a [cloud.nan.builders](https://cloud.nan.builders) e iniciar sesión 2. Crear un nuevo **Space** apuntando al repositorio GitHub 3. Configurar el puerto (debe coincidir con `EXPOSE` del Dockerfile) 4. El espacio tarda segundos en aprovisionarse **Síntoma de espacio no creado:** La URL (`<app>-<org>-<project>.apps.nan.builders`) devuelve **404 directamente de Cloudflare** (no 502, no timeout). El DNS resuelve a IPs de Cloudflare pero no hay backend. > ⚠️ No basta con tener el Dockerfile en el repo. **El espacio debe existir en NaN** para que el auto-deploy por polling funcione. Sin espacio, el push a GitHub no tiene efecto visible. ### ⚠️ CRÍTICO: Renombrar repo en GitHub rompe deploy NaN Cuando renombras un repositorio en GitHub (`Ntizar/TimeIneco` → `Ntizar/Time`), **el espacio NaN existente deja de funcionar** porque el DNS de NaN apunta al nombre viejo. El espacio NaN sigue vinculado al repo viejo y el polling de GitHub ya no lo detecta. **Síntomas:** - URL anterior (`timeineco-ntizar-ntizar.apps.nan.builders`) devuelve 404 o 502 - URL nueva (`time-ntizar-ntizar.apps.nan.builders`) no existe - Push al repo renombrado no genera redeploy **Fix:** 1. Ir a [cloud.nan.builders](https://cloud.nan.builders) 2. Crear un **nuevo espacio** apuntando al repo renombrado (`Ntizar/Time`) 3. Configurar variables de entorno (ORS_API_KEY, NAP_API_KEY, PORT) 4. Configurar Container Port (debe coincidir con EXPOSE del Dockerfile) 5. Esperar primer build Kaniko (~2-5 min) 6. Verificar: `curl -s https://time-ntizar-ntizar.apps.nan.builders/healthz` **NO se puede renombrar un espacio NaN** — hay que crear uno nuevo y (opcionalmente) borrar el viejo tras confirmar que el nuevo funciona. **Patrón de URL:** `<espacio>-<owner>-<owner>.apps.nan.builders` — el owner aparece duplicado en la URL. ### Patrón de URL NaN.builders asigna URLs con el patrón: ``` <app-name>-<owner>-<owner>.apps.nan.builders ``` Donde `app-name` es el nombre del espacio en NaN, y `owner` es el usuario/organización de GitHub. El owner aparece **duplicado** en la URL. ### Opciones de hosting alternativas Si el espacio NaN aún no existe y la URL es urgente: | Opción | Requisito | Comando | |--------|-----------|---------| | **GitHub Pages** | Repo público (o plan pago para privado) | `gh repo edit --visibility public && gh api -X POST repos/:owner/:repo/pages -f source.branch=gh-pages` | | **Surge.sh** | Login interactivo (email + password) primero | `npm i -g surge && surge ./dist nombre.surge.sh` | | **Vercel/Netlify** | Login vía CLI | `npx vercel deploy --prebuilt --prod` | ### ⚠️ `.env` en `.dockerignore` mata tokens de API Si añades `.env` a `.dockerignore`, el archivo NO se copia al contenedor Docker. El servidor arranca pero `process.env.NAN_API` no existe (NaN no hereda env vars del host) y el fallback a `.env` local también falla (no está en el contenedor). **Todos los endpoints de IA (estimación comida, ejercicio, coach) devuelven "Token no configurado".** **Síntoma:** App funciona, login OK, datos se guardan, pero todo lo que usa IA falla silenciosamente. **Fix:** Quitar `.env` de `.dockerignore`. El `.env` DEBE estar en el contenedor para que el fallback `fs.readFileSync('.env')` funcione. El `.env` ya está en `.gitignore` (no se sube a GitHub), pero el `COPY . .` del Dockerfile lo copia al contenedor. ```dockerignore # ❌ MAL — .env no llega al contenedor node_modules/ .env # ✅ BIEN — .env se copia al contenedor node_modules/ .git .gitignore ``` ### ⚠️ CRÍTICO: `"type": "module"` + `require()` = crash silencioso Si `package.json` tiene `"type": "module"`, Node.js ejecuta TODOS los `.js` como ESM. Si el server usa `require()` (CommonJS), el contenedor **crashea al arrancar** y el pod se queda en `Pending` — **sin error visible en el dashboard de NaN**. **Síntomas:** Build Kaniko exitoso, `Current Image` se llena, pero URL siempre devuelve 503 o 404. **Causa típica:** Dockerfile con `RUN echo 'require("http")...' > server.js` (inline CommonJS) en un proyecto Vite que tiene `"type": "module"`. **Fix:** 1. Crear `server.mjs` como archivo separado en el repo (ESM puro: `import http from "node:http"`) 2. Usar `COPY --chown=appuser:appgroup server.mjs ./` en el Dockerfile 3. Cambiar `CMD ["node", "server.js"]` → `CMD ["node", "server.mjs"]` 4. **NUNCA** hacer `echo 'require(...)' > server.js` en un proyecto con `"type": "module"` **Verificación:** `grep '"type"' package.json` + `grep 'require(' server.js` — si ambos dan resultado, hay conflicto ESM/CJS. ### Puerto - El espacio de NaN puede estar configurado en cualquier puerto (3500, 3700, 4000, 4500, 6000, etc.) - **El Dockerfile EXPOSE debe coincidir con el puerto del espacio** - **El server.js debe usar `process.env.PORT || <puerto>` como default** - **Para sitios estáticos con nginx: el EXPOSE y el `listen` en nginx.conf deben coincidir** — no asumir que nginx escucha en 80 por defecto - Si no coinciden → 502 Bad Gateway - ⚠️ **NUNCA intentar escuchar en puerto 80 desde un contenedor no-root**. Cuando el Dockerfile usa `USER appuser` (que es obligatorio), el bind a puerto 80 **falla silenciosamente** — Node.js no levanta, el proceso muere, y el pod se queda en Pending. **Solo escuchar en el puerto configurado**: `http.createServer(handler).listen(process.env.PORT || 3700, "0.0.0.0")` - **HEALTHCHECK debe apuntar al puerto configurado**, no a 80: `CMD wget -qO- http://localhost:3700/healthz || exit 1` ### Usuario no-root (crítico) - **NaN BLOQUEA contenedores que ejecutan como root** — el pod se queda en "Pending" indefinidamente aunque Kaniko construya la imagen correctamente - **Siempre crear usuario no-root**: `RUN addgroup -S appgroup && adduser -S appuser -G appgroup` - **Siempre cambiar antes del CMD**: `USER appuser` - **Ajustar permisos** de todos los archivos copiados: `RUN chown -R appuser:appgroup /app` - Es el **mismo patrón** indispensable que usa esios-dashboard - Síntoma: Kaniko build exitoso (se ve en logs) pero URL devuelve 404 (Cloudflare) → el pod está en Pending → revisar que el Dockerfile use no-root ### NaN auto-polling (semi-automático) - NaN **NO** usa webhooks de GitHub - NaN **SÍ** hace polling periódico del repositorio GitHub (cada ~1-5 minutos, depende de carga) - Cuando detecta un nuevo commit en `main`, **reconstruye y redeploya automáticamente** - Si la build anterior falló (ej: faltaba Dockerfile, o el contenedor era root), NaN **reintenta con el siguiente commit** — no hace falta ir al dashboard obligatoriamente - Para forzar inmediatamente: dashboard → [cloud.nan.builders](https://cloud.nan.builders) → **Redeploy** - `git commit --allow-empty -m "trigger redeploy" && git push` **SÍ funciona** indirectamente: el push a GitHub → NaN detecta cambio en su próximo ciclo de polling → reconstruye ### Variables de entorno - Se configuran en la web de NaN → pestaña **Env** - NO se suben por Git - Se necesitan para: API keys, tokens, URLs - `.env.example` en el repo sirve como documentación ### Kaniko - No soporta `--build-arg` para secrets - Las secrets VAN en variables de entorno, no en el Dockerfile - El build puede fallar si `package-lock.json` no coincide con `package.json` - **⚠️ `npm ci` falla si package-lock.json está desincronizado** — si añades nuevas dependencias (bcryptjs, express-session, etc.), el lockfile queda obsoleto. Regenerar con `npm install` antes de commit. Síntoma: build Kaniko exitoso pero contenedor crash al arrancar porque faltan módulos. - **Error `error resolving dockerfile path: please provide a valid path to a Dockerfile within the build context with --dockerfile`** → No hay Dockerfile en la raíz del repo, o el Dockerfile está mal nombrado (debe llamarse exactamente `Dockerfile`) - Si el build falla (ej: no existía Dockerfile), NaN reintenta automáticamente con el siguiente commit detectado por polling ### Dockerfile single-stage con build interno + Node.js (Vite static) Para proyectos Vite que necesitan build y `node:alpine` en una sola etapa (más simple que multi-etapa): ```dockerfile FROM node:20-alpine WORKDIR /app ENV NODE_ENV=production ENV PORT=3700 # 1. Dependencias COPY package.json package-lock.json ./ RUN npm ci --include=dev # --include=dev para que Vite esté disponible en build # 2. Código fuente COPY index.html vite.config.js ./ COPY public/ ./public/ COPY src/ ./src/ # 3. Build RUN npx vite build # 4. Usuario no-root (requisito NaN) RUN addgroup -S appgroup && adduser -S appuser -G appgroup # 5. Servidor HTTP Node.js embebido (SPA + multi-puerto) RUN echo 'const http = require("http"); \ const fs = require("fs"); \ const path = require("path"); \ const DIST = path.join(__dirname, "dist"); \ const MIME = { \ ".html": "text/html", ".css": "text/css", \ ".js": "application/javascript", ".json": "application/json", \ ".png": "image/png", ".jpg": "image/jpeg", ".svg": "image/svg+xml", \ ".ico": "image/x-icon", ".woff2": "font/woff2" \ }; \ function handler(req, res) { \
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen