Guía9 min

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.

DockerCloudflare
Contenedor de agente contra un techo de memoria y un proceso que se reinicia

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:

  1. Heap de V8 (FATAL ERROR: Reached heap limit) → --max-old-space-size.
  2. 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

Techo de memoria del runtime frente al heap de Node

RuntimeTecho verificado 2026-09-03Qué medir
Docker--memory (mín. 6m); default ilimitadoRSS vs cgroup
Flypreset Machine (p. ej. 256 MB shared-cpu-1x)dashboard + fly status
RailwayUSD 10/GB-mes; Free 0,5 GB techo serviciométricas del panel
Workers128 MB/isolate (Free y Paid)1102 / exceededMemory
Vercel FunctionsHobby 2 GB; Pro máx. 4 GBinvocación, no daemon
Node heap--max-old-space-size en MiBheap limit / FATAL ERROR

Errores comunes

Proceso del agente reiniciado por el OOM killer

SíntomaCausaFix
Restart sin stackOOM del cgroupsube RAM o baja RSS
heap limitV8, no Dockersube old-space o recorta buffers
Worker 1102 / OOMPDF en memoriano proceses blobs en Workers
VPS entero se congelaDocker sin mem_limitpon el límite
Railway caro idleRAM oversize 24/7techo real + autostop

Relación con el resto

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.