Límites de memoria y OOM de un agente IA: Docker, Fly, Workers
Resumen
Cómo no morir a mitad de un tool call: mem_limit de Docker, tamaño de Machine en Fly, RAM de Railway, 128 MB de Workers y --max-old-space-size de Node. El OOM del kernel no es el heap de V8. Mide RSS, no la fe.

Qué resuelve
Esta pieza se queda en la decisión práctica: qué instalar, qué riesgo agrega y cómo aplicarlo sin romper operación.
Un agente que carga un PDF, un RAG local y el historial del chat se come la RAM. El síntoma es un restart silencioso, no un stack trace bonito. Cold start cubre el primer hit lento. Concurrencia cubre réplicas. Esta guía cubre el techo de un solo proceso. Fuentes oficiales consultadas el 3 de septiembre de 2026.
La noticia de Elastic Builds de Vercel habla de OOM en el build. Aquí es OOM en runtime.
La regla
Hay dos muertes:
- Heap de V8 (
FATAL ERROR: Reached heap limit) →--max-old-space-size. - cgroup / kernel OOM (Docker
mem_limit, Fly RAM) → el PID desaparece, a veces sin log.
Si el heap de Node es 4 GB y el contenedor es 512 MB, gana el kernel. Alinea los dos: heap por debajo del límite del cgroup (deja ~100 MB al resto).
Docker
Por defecto el contenedor no tiene techo: usa tanta RAM como el scheduler del host permita. --memory / Compose mem_limit pone el hard limit; el mínimo documentado es 6m. Sin límite, el agente se come el VPS y el kernel mata otros procesos (incluido, a veces, el daemon). Con límite bajo, el OOM killer mata el agente. --memory-swap solo tiene sentido si también hay --memory: si no lo pones, el contenedor puede usar otro tanto de swap (300m de RAM → 600m RAM+swap si el host tiene swap). Si --memory y --memory-swap son el mismo número, no hay swap. No pongas --oom-kill-disable sin -m: el host se queda sin RAM. No bajes --oom-score-adj a un negativo extremo para “proteger” el bot. Documenta el número.
Fly / Railway
El tamaño de Machine (RAM) es el cgroup. Subir CPU no baja RSS. Un sqlite + embeddings en RAM no cabe en 256 MB. Fly publica presets: shared-cpu-1x arranca en 256 MB (~USD 2,02/30 días) y sube a 512 MB / 1 GB / 2 GB; RAM extra ~USD 5/GB-30 días. Mira el dashboard de RSS durante un turno real, no en /health. Railway cobra USD 10/GB-mes de RAM (minutal). Techos por servicio (plans): Free 0,5 GB, Hobby 48 GB, Pro 1 TB — son máximos con réplicas, no un default. Un proceso gordo idle sale caro; kill switch / autostop no sustituyen un techo.
Workers
128 MB por isolate en Free y Paid (docs de limits, 3 sep 2026). El techo es del isolate, no de cada request: un isolate atiende muchas invocaciones concurrentes. No hay mem_limit que subir. Un JSON enorme o un arrayBuffer del PDF te echa. Cloudflare responde Error 1102 (Worker exceeded resource limits); en analytics el outcome es exceededMemory. También puedes ver Memory limit would be exceeded before EOF si bufferizas un body que no cabe. Parte el trabajo: stream del body, KV/R2/D1 para blobs, no RAG local en el Worker.
Vercel Functions
Con Fluid: Hobby 2 GB / 1 vCPU (default = máximo). Pro/Enterprise default 2 GB / 1 vCPU, máximo 4 GB / 2 vCPU. Body de request o response: 4,5 MB (FUNCTION_PAYLOAD_TOO_LARGE). Duración con Fluid: Hobby 300 s; Pro default 300 s, máximo 800 s (1800 s en beta con runtime concreto). Cada invocación es un techo distinto al de un daemon Docker. Streaming + tools largos + buffer del body = RSS. No clones el patrón “cargo todo el repo en RAM”.
Node
NODE_OPTIONS=--max-old-space-size=512
El número es MiB del old space de V8, no del contenedor. Docs: en una máquina de 2 GiB, 1536 deja margen y evita swap. Si Fly te da 1024 MB, 768 de heap es un punto de partida. Subir a 4096 en un box de 512 es inútil: el kernel mata antes. --max-old-space-size-percentage pisa el valor fijo si ambos están set.
Tabla

| Runtime | Techo verificado 2026-09-03 | Qué medir |
|---|---|---|
| Docker | --memory (mín. 6m); default ilimitado | RSS vs cgroup |
| Fly | preset Machine (p. ej. 256 MB shared-cpu-1x) | dashboard + fly status |
| Railway | USD 10/GB-mes; Free 0,5 GB techo servicio | métricas del panel |
| Workers | 128 MB/isolate (Free y Paid) | 1102 / exceededMemory |
| Vercel Functions | Hobby 2 GB; Pro máx. 4 GB | invocación, no daemon |
| Node heap | --max-old-space-size en MiB | heap limit / FATAL ERROR |
Errores comunes

| Síntoma | Causa | Fix |
|---|---|---|
| Restart sin stack | OOM del cgroup | sube RAM o baja RSS |
heap limit | V8, no Docker | sube old-space o recorta buffers |
| Worker 1102 / OOM | PDF en memoria | no proceses blobs en Workers |
| VPS entero se congela | Docker sin mem_limit | pon el límite |
| Railway caro idle | RAM oversize 24/7 | techo real + autostop |
Relación con el resto
- Réplicas no bajan RSS: concurrencia.
- Restart sucio: SIGTERM.
- Ver el kill: logs.
- Disco vs RAM: backup.
Checklist
- Límite de RAM escrito (Compose / Fly / Railway)
-
--max-old-space-size< ese límite - Un turno real medido (RSS p95)
- Workers: nada > decenas de MB en un request
- Docker en VPS con
mem_limit - Ensayo: carga grande → log claro o 503, no kernel silencioso
FAQ
¿Más réplicas? Dos procesos gordos = 2× RSS. No es el fix.
¿Swap? Esconde el OOM y mata la latencia del turno. No.
¿Embeddings en proceso? Sale del heap. Disco o servicio aparte.
El ensayo: manda un adjunto grande al bot de Preview. O hay rechazo controlado o hay log de heap. Si el proceso “se va”, era el kernel.
No persigas “dejar RAM de sobra por si el modelo crece”. El modelo no vive en tu proceso; vive en la API. Lo que crece es tu contexto, tools y archivos. Recorta eso antes de pagar una Machine de 4 GB.
Siguiente paso: si la RAM ya cabe y igual se cae, healthchecks y alertas. Sin runtime: curso.
Lecturas relacionadas
Sigue explorando Deploy y otras piezas para builders.



