El build que se quedo sin memoria: Vite pidiendo 2.5 GB en una maquina con 843 MB
El contenedor moria sin mensaje y docker compose build decia exit code 137. El numero estaba en el Dockerfile: NODE_OPTIONS pedia dos veces y media la memoria que habia libre.
El despliegue se caía siempre en el mismo punto:
=> ERROR [assets 7/7] RUN npm run build
------
failed to solve: process "/bin/sh -c npm run build" did not complete
successfully: exit code: 137
Sin traza. Sin error de JavaScript. Sin nada que buscar en Google que no fueran diez respuestas distintas.
Qué es el 137
El 137 es 128 + 9. El 9 es SIGKILL. Nadie decidió terminar ese proceso: el kernel lo mató porque la máquina se quedó sin memoria y él era el que más pedía.
Es importante entender que no es un error de tu código. Es el sistema operativo defendiéndose. Y por eso no hay traza: al proceso no le dio tiempo de escribir nada.
Se confirma en el registro del kernel:
sudo dmesg -T | grep -i "killed process"
Si ahí aparece tu proceso de Node, ya no hay que seguir buscando en el JavaScript.
Dónde estaba el número
En el Dockerfile de esa aplicación, heredado de cuando el proyecto se construía en mi laptop:
ENV NODE_OPTIONS=--max-old-space-size=2560
Eso le dice a Node que puede crecer su montón hasta 2.5 GB. En mi laptop de 32 GB es una línea sensata. En el VPS, en el momento del build, había esto:
total used free buff/cache available
Mem: 3613 2653 211 1049 843
843 MB disponibles. Node estaba autorizado a pedir tres veces eso. Y cuando lo pedía, el kernel lo mataba.
Lo que hace confuso el diagnóstico es que --max-old-space-size no reserva memoria: es un techo. Con un proyecto pequeño nunca se acerca y el mismo Dockerfile funciona sin problema. El día que el frontend crece lo suficiente para pasar de 843 MB, empieza a fallar — y como no cambiaste el Dockerfile, buscas la causa en el código nuevo.
El arreglo, en dos partes
Bajar el techo a algo que quepa:
ENV NODE_OPTIONS=--max-old-space-size=1536
Con 1536 el build pasó en 64 segundos. No es magia: es que un techo por debajo de la memoria real hace que Node recolecte basura más seguido en vez de crecer hasta que lo maten. Tarda un poco más y termina.
Hacer espacio antes de construir. Apago lo prescindible, construyo, y lo vuelvo a levantar:
docker stop netdata n8n-pipe n8n-bache uptime-kuma
docker compose build
docker start netdata n8n-pipe n8n-bache uptime-kuma
Eso libera unos 650 MB. Tarda medio minuto en que todo vuelva a responder —n8n y Spring Boot arrancan lento— y es reversible, que es lo que lo hace cómodo.
Cómo saber qué número poner
No lo adivines. Mira qué hay libre en el momento en que vas a construir:
free -m
La columna que importa es available, no free. free es memoria que nadie está tocando; available incluye la caché que el kernel puede soltar si hace falta, que es la que de verdad puedes usar.
Mi regla es dejar el techo de Node entre el 60% y el 70% de available. Con 900 MB libres, 1536 ya va justo; con 600, hay que apagar cosas primero o no va a pasar.
Lo que aprendí de fondo
Un Dockerfile no es portable solo porque esté en Docker. Las constantes de tiempo de compilación —memoria, paralelismo, tiempos de espera— se escriben pensando en la máquina donde uno desarrolla, y viajan al servidor sin que nadie las revise.
Desde entonces, cuando muevo un proyecto a una máquina más chica, lo primero que hago es buscar los números escritos a mano:
grep -rn "max-old-space-size\|--max_old_space\|-j[0-9]\|memory_limit" Dockerfile docker/
Casi siempre hay uno, y casi siempre está pensado para una máquina que ya no es esta.