Tu API funciona en localhost:3000. Llevarla a un servidor real implica resolver preguntas
que en desarrollo no existían: ¿quién la vuelve a levantar si se cae?, ¿cómo se sirve por HTTPS?, ¿dónde
van las contraseñas?, ¿qué pasa con las peticiones en curso al desplegar una versión nueva? En el
libro Node.js desde cero el último capítulo está dedicado al despliegue en
producción en Heroku, un VPS o Docker; acá tenés las dos opciones más habituales y un checklist final.
Preparar la aplicación
- Configuración por variables de entorno: puerto, credenciales de bases de datos, secretos. Nada de eso escrito en el código.
NODE_ENV=production: muchas librerías, Express incluido, activan optimizaciones y ocultan detalles de errores con este valor.- Escuchar en el puerto que te den:
process.env.PORT ?? 3000. - Un script
startenpackage.json:"start": "node src/server.js". - Una ruta de salud para monitorización y balanceadores:
app.get('/health', (req, res) => {
res.json({ estado: 'ok', uptime: Math.round(process.uptime()) });
});
Opción 1: VPS con PM2 y Nginx
En un VPS (un servidor Linux propio en la nube), PM2 mantiene la aplicación en marcha, la reinicia si falla, la arranca con el sistema y puede ejecutar una instancia por núcleo de CPU. Nginx se coloca delante: gestiona HTTPS, comprime y reenvía las peticiones a Node.js.
$ git clone https://github.com/tu-usuario/api-blog.git && cd api-blog
$ npm ci --omit=dev # instala exactamente el lockfile, sin devDependencies
$ npm install -g pm2
$ pm2 start ecosystem.config.cjs
$ pm2 save && pm2 startup # arrancar automáticamente al reiniciar el servidor
module.exports = {
apps: [{
name: 'api-blog',
script: 'src/server.js',
instances: 'max', // una instancia por núcleo de CPU
exec_mode: 'cluster',
max_memory_restart: '300M',
env: {
NODE_ENV: 'production',
PORT: 3000,
},
}],
};
server {
listen 80;
server_name api.tudominio.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# necesarias para WebSocket / Socket.IO
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Después, sudo certbot --nginx -d api.tudominio.com obtiene un certificado gratuito de
Let's Encrypt y configura HTTPS. Como Node.js queda detrás de un proxy, añadí
app.set('trust proxy', 1) en Express para que req.ip y
req.protocol sean los reales.
Opción 2: Docker
Docker empaqueta la aplicación con su versión exacta de Node.js y sus dependencias. La misma imagen funciona en tu máquina, en el servidor y en cualquier plataforma de contenedores.
FROM node:22-alpine
ENV NODE_ENV=production
WORKDIR /app
# Primero solo las dependencias: Docker reutiliza esta capa si no cambian
COPY package*.json ./
RUN npm ci --omit=dev
COPY src ./src
# No ejecutar como root
USER node
EXPOSE 3000
CMD ["node", "src/server.js"]
$ docker build -t api-blog .
$ docker run -d --name api-blog -p 3000:3000 --env-file .env --restart unless-stopped api-blog
Añadí un .dockerignore con node_modules, .env y .git
para que no terminen dentro de la imagen. Con --restart unless-stopped, Docker cumple el papel
de PM2: si el proceso se cae, lo vuelve a levantar.
CMD ejecuta node directamente y no npm start:
así las señales de apagado del contenedor llegan a tu proceso.
Apagado ordenado
Al desplegar una versión nueva, el proceso viejo recibe la señal SIGTERM. Si se corta en
seco, las peticiones en curso fallan. Mejor dejar de aceptar conexiones nuevas, terminar las actuales y
cerrar los recursos:
import app from './app.js';
const server = app.listen(process.env.PORT ?? 3000, () => {
console.log(`API escuchando en el puerto ${server.address().port}`);
});
function apagar(senal) {
console.log(`${senal} recibido: cerrando...`);
server.close(async () => {
// aquí: cerrar el pool de MySQL, el cliente de MongoDB, etc.
process.exit(0);
});
// si en 10 s no terminó, salir igualmente
setTimeout(() => process.exit(1), 10_000).unref();
}
process.on('SIGTERM', () => apagar('SIGTERM'));
process.on('SIGINT', () => apagar('SIGINT'));
Checklist de producción
NODE_ENV=productiony secretos en variables de entorno, nunca en el repositorio.- Dependencias instaladas con
npm ci --omit=devy revisadas connpm audit. - HTTPS delante (Nginx, balanceador o la plataforma) y
trust proxyconfigurado. - Cabeceras de seguridad con
helmety límite de peticiones en las rutas sensibles (ver middleware en Express). - Gestor de procesos (PM2 o Docker) que reinicie la aplicación si se cae.
- Apagado ordenado con
SIGTERM. - Logs en un formato que puedas buscar y una ruta
/healthmonitorizada. - Copias de seguridad de la base de datos, y una restauración probada al menos una vez.
- La aplicación no se ejecuta como root.
Del código a producción
El libro Node.js desde cero termina con el despliegue en producción en Heroku, un VPS o Docker, además de pruebas y seguridad.
Comprar en Amazon