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.

Ir para a instalação

Informações da origem

Repositório
Ntizar/NtizarBrainMasterMind
Última atividade na origem
26 de junho de 2026 às 12:05
Idioma detectado do SKILL.md
espanhol
Estrelas
2
Forks
0

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Explorador de arquivos
10 arquivos

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
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) { \
Ver no GitHub
Este SKILL.md e muito grande, entao o SkillsMP mostra aqui apenas a primeira secao. Ver no GitHub