Chromium pesa aproximadamente 280 MB. El límite de AWS Lambda para un paquete descomprimido es de 250 MB. Si alguna vez intentaste npm install puppeteer y subirlo directo a Lambda, ya sabes cómo termina la jugada: no funciona.
He pasado suficientes madrugadas depurando errores de "Failed to launch the browser process" como para saber que este tema merece una comparación de verdad, no otro tutorial que te enseña un solo camino y te deja adivinando los otros dos. Así que eso es lo que vas a encontrar aquí: Layers vs. Container Images vs. carga directa en ZIP, una matriz de compatibilidad realmente actualizada para 2026 y una sección de solución de problemas con los cinco errores que, estadísticamente, tienes más probabilidades de encontrarte.
Qué es Puppeteer en AWS Lambda y por qué vale la pena
Puppeteer es una librería de Node.js que controla Chromium en modo headless a través de Chrome DevTools Protocol. Lambda es la computación sin servidor de AWS: pagas por invocación, escala sola y no tienes que estar pendiente de servidores. Si combinas ambas cosas, obtienes una configuración de automatización de navegador capaz de lanzar cientos de ejecuciones en paralelo sin aprovisionar ni una sola instancia EC2.
Los casos de uso suelen repetirse entre equipos: scraping web, generación de capturas y PDF, monitorización sintética, pre-renderizado de aplicaciones de una sola página para SEO y pruebas automatizadas de UI. El problema siempre es el mismo que ya comenté: el tamaño de Chromium frente a los límites de empaquetado de Lambda. Por eso nadie despliega el puppeteer completo —que además descarga su propio Chromium— en Lambda. En su lugar, se usa puppeteer-core (sin navegador incluido) junto con un binario de Chromium optimizado para Lambda, normalmente @sparticuz/chromium.
Ese único cambio —usar puppeteer-core en lugar de puppeteer— resuelve el 80% del problema de tamaño antes incluso de escribir una línea de configuración de despliegue.
Layers vs. Container Image vs. ZIP: elige primero tu camino
Hay algo que me fastidió mientras investigaba esto: casi todas las guías existentes cubren exactamente un método de despliegue. Los tutoriales de AWS SAM usan Layers. Los ejemplos de CDK usan Docker. Un post cualquiera en Substack usa un ZIP puro con un binario de Chromium alojado en S3. Nadie los pone cara a cara, así que la primera decisión real que necesitas tomar —qué método de despliegue encaja con mi caso— se salta por completo.
Así que vamos a corregir eso.
| Criterio | Lambda Layers | Imagen de contenedor (Docker) | Carga directa en ZIP |
|---|---|---|---|
| Tamaño máximo del paquete | 250 MB descomprimidos (sumando todas las layers) | Imagen de 10 GB | 250 MB descomprimidos |
| Complejidad del despliegue | Media (gestión de ARN de layers) | Mayor (Dockerfile + push a ECR) | La más baja (zip y subir) |
| Impacto en el cold start | Moderado | Algo mayor (más tiempo para descargar la imagen) | Moderado |
| Flujo de actualización de Chromium | Volver a publicar la versión de la layer | Reconstruir la imagen | Volver a subir el zip |
| Ideal para | Prototipos rápidos, usuarios de Serverless Framework | Cargas de producción, equipos con CI en Docker | Funciones puntuales y sencillas |
| Soporte de IaC | SAM, Serverless Framework | CDK, SAM, Terraform | Consola, cualquier IaC |
Tanto el límite de 250 MB como el de 10 GB salen directamente de la documentación oficial de cuotas de Lambda de AWS: no es una cifra que haya cambiado mucho, pero sí es la restricción que define toda tu estrategia de despliegue desde el principio.
Mi regla práctica, por si te sirve: si estás prototipando o ya usas Serverless Framework, empieza con Layers. Si vas a producción y tu equipo ya tiene CI/CD con Docker, ve con Container Image: el techo de 10 GB te da bastante margen. Si solo necesitas una función para sacar capturas de vez en cuando, el ZIP directo es lo menos engorroso.
Las tres rutas usan la misma combinación base de dependencias: puppeteer-core + @sparticuz/chromium. El método de despliegue cambia cómo empaquetas esa combinación, no qué empaquetas.

La matriz de compatibilidad de versiones para 2026 (deja de adivinar)
Esta es la parte que de verdad le cuesta meses a la gente, no horas. La queja más habitual en Stack Overflow y en issues de GitHub no es "cómo lo despliego", sino "por qué mi despliegue que funcionaba se rompió en silencio después de una actualización de npm". El culpable casi siempre es un desajuste entre @sparticuz/chromium, puppeteer-core y el runtime de Node.js.
Primero, lo importante: chrome-aws-lambda (el paquete original de alixaxel) está obsoleto. No funciona bien en Node 18+ y no ha seguido el ritmo de las versiones de Chromium. Si te topas con un tutorial que lo menciona, cierra la pestaña: estás leyendo algo desactualizado. Toda guía actual debería mandarte a @sparticuz/chromium.
Usa esta regla de compatibilidad en vez de emparejar versiones mayores a ojo:
| Componente | Regla de versión | Qué verificar antes de desplegar |
|---|---|---|
puppeteer-core | Elige la versión de Puppeteer que necesita tu aplicación | Revisa qué versión de Chromium admite esa release de Puppeteer |
@sparticuz/chromium | Su versión mayor sigue la versión mayor de Chromium, no la de Puppeteer | Coincídela con la build de Chromium de la tabla de compatibilidad de Puppeteer y revisa las notas de release de Sparticuz |
| Runtime de Node.js en AWS Lambda | Usa un runtime de Lambda actualmente soportado | Ejecuta una prueba de invocación después de cada actualización de runtime o paquete |
| Arquitectura | El paquete npm incluye binarios x64; el soporte arm64 empieza con Chromium v135 mediante una layer arm64 o un remote pack | Haz coincidir exactamente la arquitectura de Lambda, el artefacto de layer/pack y la versión de Chromium |
No estoy fijando aquí una pareja de paquetes concreta a propósito, porque @sparticuz/chromium sigue el ciclo de lanzamiento de Chromium y no usa el versionado semántico normal. Empieza por la página oficial de compatibilidad de Chromium de Puppeteer, anota la versión mayor de Chromium admitida por la release de Puppeteer que elijas y luego selecciona esa misma versión mayor de @sparticuz/chromium. Por último, lee las notas de release de Sparticuz para cambios incompatibles a nivel de parche y detalles de arquitectura. No instales el mismo número de versión mayor en ambos paquetes salvo que esa relación esté confirmada por esas dos fuentes.

Cómo desplegar Puppeteer en AWS Lambda con Lambda Layers
Una Lambda Layer te permite empaquetar Chromium por separado del código de tu función, lo que mantiene pequeño el handler y te deja reutilizar la misma layer de Chromium en varias funciones. Es lo más parecido a un "quick start" en este terreno.
Paso 1: Instala puppeteer-core y el paquete -min
Cuando los archivos de Chromium viven en una Lambda Layer, mantén pequeño el paquete de la función usando @sparticuz/chromium-min. Sustituye los marcadores por las versiones compatibles que confirmaste arriba:
npm install puppeteer-core@$PUPPETEER_VERSION \
@sparticuz/chromium-min@$CHROMIUM_VERSION
Estás instalando puppeteer-core, no puppeteer, porque se salta la descarga automática del navegador. El paquete -min aporta las ayudas para el lanzamiento, mientras que la layer proporciona los archivos de Chromium comprimidos con Brotli bajo /opt/chromium.
Paso 2: Crea o referencia una Lambda Layer de Chromium
Usa el archivo de layer específico para la arquitectura que viene adjunto a una release oficial de Sparticuz, o genera el archivo desde el repositorio oficial. Para Lambda x86_64, la compilación documentada es:
git clone --depth=1 https://github.com/sparticuz/chromium.git
cd chromium
make chromium.x64.zip
Eso genera chromium.x64.zip. Súbelo a S3 y publícalo como Lambda Layer con el runtime y la arquitectura que realmente uses. Para arm64, usa el artefacto de release arm64 correspondiente o el objetivo de compilación equivalente; no adjuntes un archivo x64 a una función arm64.
Si usas SAM, adjunta el ARN de la layer directamente en tu template.yaml:
Resources:
PuppeteerFunction:
Type: AWS::Serverless::Function
Properties:
Layers:
- arn:aws:lambda:us-east-1:XXXXXXXXXXXX:layer:chromium-layer:1
Paso 3: Escribe el handler de Lambda
Aquí tienes un patrón de handler funcional que navega a una URL y devuelve el título de la página:
import puppeteer from "puppeteer-core";
import chromium from "@sparticuz/chromium-min";
export const handler = async () => {
const browser = await puppeteer.launch({
args: puppeteer.defaultArgs({ args: chromium.args, headless: "shell" }),
executablePath: await chromium.executablePath("/opt/chromium"),
headless: "shell",
});
try {
const page = await browser.newPage();
await page.goto("https://example.com", { waitUntil: "domcontentloaded" });
return { title: await page.title() };
} finally {
await browser.close();
}
};
Fíjate en el bloque finally. Cierra siempre el navegador ahí: si no lo haces, los entornos Lambda en caliente acumulan procesos de navegador zombis entre invocaciones y acabarás con errores de memoria raros que no tienen nada que ver con tu código real.
Paso 4: Configura memoria, timeout y arquitectura
Asigna al menos 1024 MB de memoria; yo recomendaría 1536–2048 MB para cualquier cosa que vaya más allá de una captura trivial. Configura el timeout en al menos 60 segundos. Fija la arquitectura en x86_64, salvo que hayas confirmado específicamente el soporte arm64 para la versión exacta de Chromium que usas (esto cambia según la release).
Paso 5: Despliega y prueba
sam build && sam deploy --guided
Invócalo con un evento de prueba y revisa CloudWatch Logs de inmediato si algo sale mal: el 90% de los errores de la sección de troubleshooting de abajo aparecen clarísimos en esos logs.
Cómo desplegar Puppeteer en AWS Lambda con Container Images (Docker)
Las imágenes de contenedor eliminan por completo el problema de los 250 MB al darte un techo de 10 GB. Suele ser la mejor opción para cargas de producción, sobre todo si tu equipo ya usa Docker en la cadena de CI.
Paso 1: Crea el Dockerfile
Parte de una imagen base oficial de AWS Lambda para Node.js, instala tus dependencias y define el handler:
FROM public.ecr.aws/lambda/nodejs:20
COPY package*.json ./
RUN npm install --production
COPY . .
CMD ["index.handler"]
Dependiendo de tu paquete de Chromium, quizá tengas que hacer yum install de algunas librerías compartidas (más sobre esto en la sección de troubleshooting): @sparticuz/chromium incluye la mayoría de lo necesario, lo que reduce bastante esta fricción comparado con instalar Chrome completo a mano.
Paso 2: Compila y sube a Amazon ECR
aws ecr create-repository --repository-name puppeteer-lambda
docker build -t puppeteer-lambda .
docker tag puppeteer-lambda:latest <account-id>.dkr.ecr.<region>.amazonaws.com/puppeteer-lambda:latest
aws ecr get-login-password | docker login --username AWS --password-stdin <account-id>.dkr.ecr.<region>.amazonaws.com
docker push <account-id>.dkr.ecr.<region>.amazonaws.com/puppeteer-lambda:latest
Mantén la imagen en la misma región que tu función Lambda: las descargas cross-region añaden latencia innecesaria.
Paso 3: Crea la función Lambda desde la imagen de contenedor
Apunta tu función al URI de la imagen en ECR mediante CLI o CDK, y ajusta memoria (1536–2048 MB) y timeout (60–120 segundos) de la misma manera que lo harías con un despliegue basado en layers.
Paso 4: Despliega y prueba
Invoca con un evento de prueba y verifica la salida. La principal desventaja frente a Layers: un cold start algo mayor por la descarga de una imagen más pesada, pero ganas muchísimo margen para dependencias.
Cómo desplegar Puppeteer en AWS Lambda con carga directa en ZIP
Esta es la opción sin adornos: sin layers que gestionar, sin Docker que construir. Va bien para prototipos o para una sola función que no necesita escalar hasta convertirse en una plataforma completa de automatización de navegador.
Paso 1: Instala las dependencias localmente
Para un ZIP autocontenido, usa puppeteer-core + @sparticuz/chromium y fija ambas versiones. El paquete completo incluye los archivos de Chromium comprimidos y los extrae a /tmp en tiempo de ejecución. Usa @sparticuz/chromium-min solo cuando esos archivos se suministren aparte mediante una Lambda Layer o una URL rápida de remote pack; el paquete -min no incluye por sí mismo los archivos Brotli.
Paso 2: Empaqueta y comprime la función
npm install --production
zip -r function.zip . -x "*.git*"
El flag --production importa aquí: las dependencias de desarrollo se comen tu presupuesto de 250 MB sin aportar nada.
Paso 3: Sube y configura la función Lambda
aws lambda update-function-code --function-name my-puppeteer-fn --zip-file fileb://function.zip
Si tu ZIP supera los 50 MB, no podrás subirlo directamente desde la consola ni con una simple llamada CLI: tendrás que subirlo primero a S3 y referenciar el URI de S3 en su lugar. Configura memoria, timeout y arquitectura igual que en los dos métodos anteriores.
Paso 4: Despliega y prueba
Usa el mismo flujo de invocar y revisar logs. Con el paquete completo, chromium.executablePath() no necesita argumento. Con chromium-min, pasa la ruta exacta de la layer o la URL del remote pack; por ejemplo, chromium.executablePath("/opt/chromium") para la estructura de layer anterior. Un remote pack añade trabajo de descarga al primer cold start, así que hospédalo cerca de la función y verifica la versión del artefacto y la arquitectura.
Los argumentos de puppeteer.launch() que sí funcionan en Lambda
Este es el fragmento que todo el mundo copia y pega, así que vamos a hacerlo bien. El entorno de ejecución de Lambda no tiene /dev/shm, no da acceso a GPU y funciona con permisos restringidos; eso significa que la llamada por defecto a puppeteer.launch() que va bien en tu portátil simplemente... no funciona aquí.
const viewport = {
width: 1920,
height: 1080,
deviceScaleFactor: 1,
isMobile: false,
hasTouch: false,
isLandscape: true,
};
const browser = await puppeteer.launch({
args: await puppeteer.defaultArgs({ args: chromium.args, headless: "shell" }),
executablePath: await chromium.executablePath(),
headless: "shell",
defaultViewport: viewport,
});
El array chromium.args de @sparticuz/chromium ya incorpora los flags que importan en un entorno sin servidor: --no-sandbox, --disable-gpu, --disable-dev-shm-usage y similares. Ese es precisamente el valor de usar este paquete en lugar de inventarte tu propia lista de flags: sigue los requisitos de Chromium por ti.

Troubleshooting: 5 errores que todo desarrollador acaba viendo
Ninguna guía que encontré incluye una sección de troubleshooting decente, lo cual resulta raro considerando que los errores son casi seguro la razón por la que estás leyendo este artículo.
"Failed to launch the browser process"
Causa raíz: faltan librerías compartidas (libnss3.so, libatk, etc.) o executablePath incorrecto.
Solución: @sparticuz/chromium incluye la mayoría de dependencias necesarias, por eso se recomienda frente a construir tu propio binario de Chromium. En despliegues con Docker, si sigue ocurriendo, instala explícitamente las librerías que faltan con yum install en tu Dockerfile.
"Unzipped size must be smaller than 262144000 bytes"
Causa raíz: instalaste el paquete completo puppeteer, que trae su propia descarga de Chromium (~400 MB).
Solución: cambia a puppeteer-core + @sparticuz/chromium. Si de verdad necesitas más espacio, pasa al enfoque de Container Image y su techo de 10 GB.
"Browser disconnected" o timeout en browser.newPage()
Causa raíz: memoria de Lambda insuficiente, o faltan flags como --disable-gpu en los argumentos de lanzamiento.
Solución: asigna al menos 1024 MB de memoria (yo subiría más: mira los benchmarks más abajo) y asegúrate de pasar chromium.args en lugar de una lista personalizada recortada.
El código que funcionaba se rompe tras actualizar el runtime de Lambda
Causa raíz: AWS aplica parches periódicos al runtime subyacente, lo que puede cambiar versiones de librerías compartidas o de parches de Node.js por debajo de tu aplicación.
Solución: fija explícitamente la versión de @sparticuz/chromium, fija la versión de Node en la configuración de la función y —esta es la parte que la gente suele saltarse— vuelve a probar después de cada anuncio de runtime de AWS, no solo cuando algo falle.
"Protocol error: Connection closed" después de unos 30 segundos
Causa raíz: el timeout de Lambda es más corto que el tiempo que tarda la página en cargarse y renderizarse.
Solución: sube el timeout a 60–120 segundos, define page.setDefaultNavigationTimeout() de forma explícita y cambia waitUntil: 'networkidle0' por waitUntil: 'domcontentloaded' si no necesitas esperar a que se calme cada petición de red antes de continuar.
Endurecimiento para producción: memoria, cold starts y coste
La mayoría de las guías se limitan a decirte que "subas la memoria" y ya está. Eso no es un consejo accionable: aquí tienes lo que de verdad cambia a medida que subes la memoria.
Memoria vs. rendimiento
Lambda asigna CPU de forma proporcional a la memoria, y ese es el detalle que suele despistar a la gente. Más memoria no significa solo "más RAM disponible"; también implica más CPU, lo que acelera directamente el renderizado de Chromium. En la práctica, los equipos que hacen benchmarks con Puppeteer suelen ver mejoras notables al pasar de 512 MB hasta el rango de 1536–2048 MB, aunque tus números exactos dependerán mucho de las páginas que renderices. En vez de citar una tabla de benchmarks que se quedará vieja en cuanto la leas, haz tu propia prueba en 512 MB, 1024 MB, 1536 MB y 2048 MB sobre tus páginas reales: es un ejercicio de diez minutos que te dice exactamente dónde está tu punto óptimo de coste/rendimiento.
Provisioned Concurrency para reducir cold starts
Si ejecutas algo sensible a la latencia —monitorización sintética, una API de capturas en tiempo real— los cold starts son tu enemigo. Provisioned Concurrency mantiene un número determinado de entornos de ejecución calientes y listos, eliminando el coste del cold start a cambio de pagar esa capacidad inactiva. Vale la pena precisamente cuando la latencia importa más que la eficiencia de coste pura.
arm64 (Graviton) para ahorrar costes
Las funciones Lambda basadas en Graviton cuestan aproximadamente un 20% menos que sus equivalentes x86_64. El inconveniente: históricamente, el soporte arm64 de @sparticuz/chromium ha sido más limitado que el de x86_64, así que verifica explícitamente la versión que has fijado antes de comprometerte con Graviton en producción.
VPC vs. sin VPC
Poner tu función dentro de una VPC antes añadía una latencia de cold start bastante notable; AWS ha reducido mucho esa diferencia en los últimos años, pero sigue sin ser cero. Mete tu función en una VPC solo si de verdad necesita acceder a recursos privados como RDS o ElastiCache; si no, sáltatelo.
Cuándo dejar Lambda por completo
Si tus tareas de navegador superan con frecuencia los 15 minutos, necesitan más de 10 GB de memoria o requieren sesiones de navegador persistentes entre requests, Lambda ya te está quedando corta. ECS Fargate está pensado justo para eso: cómputo de larga duración, recursos configurables y pago por segundo. Lambda es fantástica para tareas de navegador breves, explosivas y paralelizables; deja de ser la herramienta adecuada cuando tu carga empieza a parecerse a un servicio persistente.
Cuándo desplegar Puppeteer en Lambda es la opción equivocada
Vale la pena decirlo con honestidad: una gran parte de los desarrolladores que llegan a guías de "Puppeteer + Lambda" en realidad están intentando resolver un problema de extracción de datos, no de automatización de navegador. Si lo que de verdad necesitas son datos estructurados de páginas web —listados de productos, información de contacto, contenido de páginas—, todo el empaquetado de Chromium, el bloqueo de versiones y la gestión de layers de arriba es una carga que no necesitabas asumir.
Quédate con Lambda + Puppeteer si necesitas control real del navegador: interacciones personalizadas con formularios, flujos de capturas/PDF, monitorización sintética o pruebas basadas en navegador donde de verdad manipulas el DOM de forma programática.
Considera una API de scraping si tu objetivo final es sacar JSON estructurado de una página web, no conducir tú mismo una sesión de navegador. Thunderbit Open API gestiona el renderizado JS, las medidas anti-bot y los CAPTCHA mediante una sola llamada HTTP: POST /extract con un JSON Schema te devuelve datos estructurados, y POST /distill te entrega Markdown limpio. También hay un servidor MCP (thunderbit_extract, thunderbit_distill) si estás construyendo un agente de IA que necesita extraer datos en mitad del flujo sin levantar su propio navegador.
| Factor | Lambda + Puppeteer (DIY) | API de extracción (p. ej., Thunderbit) |
|---|---|---|
| Tiempo de configuración | Horas (empaquetado, layers, depuración) | Minutos (clave API + llamada HTTP) |
| Mantenimiento | Continuo (fijación de versiones, actualizaciones de runtime) | Lo asume el proveedor |
| Gestión anti-bot | Manual (stealth plugins, proxies) | Integrada |
| Formato de salida | HTML/capturas en bruto que analizas tú | JSON estructurado mediante esquema |
| Ideal para | Automatización completa del navegador, pruebas, flujos personalizados | Extracción de datos, scraping, ingesta de contenido |
Lo diré sin rodeos: si pasas horas depurando binarios de Chromium solo para sacar JSON de páginas de productos, eso suele ser señal de que estás resolviendo el problema equivocado. Guarda la ruta DIY con Lambda para cuando realmente necesites controlar un navegador; para extracción, hay un camino más directo. Si estás valorando este intercambio para un proyecto concreto, nuestra guía de AI web scrapers recorre el panorama con más detalle, y la extensión de Chrome de Thunderbit merece una prueba si quieres evaluar el enfoque de extracción primero antes de comprometerte con cualquiera de las dos rutas.
Cierre
Tres métodos de despliegue, un mismo patrón recurrente: fija tus versiones, dale a Chromium suficiente memoria para respirar y elige el método de despliegue según tus restricciones reales, no según el primer tutorial que encontraste. Layers para iterar rápido, Container Images para escalar en producción, ZIP para lo sencillo y puntual. Y si lo que realmente estás haciendo es extracción de datos y no automatización de navegador, quizá te convenga comprobar si una API de extracción diseñada para eso te ahorra por completo el dolor del empaquetado.
Preguntas frecuentes
¿Se puede ejecutar Puppeteer en AWS Lambda en 2026?
Sí, usando puppeteer-core junto con @sparticuz/chromium, desplegado mediante Layers, Container Image o ZIP directo. El paquete completo puppeteer y el paquete obsoleto chrome-aws-lambda ya no funcionan de forma fiable en los runtimes actuales de Lambda.
¿Cuál es el tamaño máximo de paquete para AWS Lambda? 250 MB descomprimidos para Layers y despliegues ZIP; 10 GB para despliegues con Container Image, según las cuotas de Lambda de AWS.
¿Sigue mantenido chrome-aws-lambda?
No. El paquete original chrome-aws-lambda (de alixaxel) está obsoleto y falla en Node 18+. Usa @sparticuz/chromium en su lugar: ahora mismo es el estándar mantenido activamente.
¿Cuánta memoria necesita Puppeteer en AWS Lambda? 1024 MB es el mínimo práctico; el rango de 1536–2048 MB es donde el rendimiento empieza a ser realmente cómodo. Por debajo de 1024 MB, espera una ejecución notablemente lenta, ya que Lambda vincula la asignación de CPU a la memoria.
¿Cómo reduzco los cold starts de Puppeteer en Lambda? Asigna más memoria —lo que también te da más CPU—, considera Provisioned Concurrency si la latencia es crítica para tu caso y mantén tu paquete de despliegue lo más ligero posible: cada dependencia extra añade tiempo de cold start.


