Chromium весит примерно 280 МБ. Лимит AWS Lambda для распакованного пакета — 250 МБ. Если вы хоть раз пробовали выполнить npm install puppeteer и сразу же задеплоить всё в Lambda, вы уже понимаете, почему эта математика не сходится.
Я провёл достаточно времени, разбирая ошибки вроде "Failed to launch the browser process" в 2 часа ночи, чтобы понять: этой теме нужен нормальный разбор, а не очередной туториал, который показывает один способ и заставляет гадать про остальные два. Поэтому здесь будет всё по делу: сравнение Layers, Container Images и прямой загрузки ZIP, актуальная на 2026 год матрица совместимости версий и раздел по отладке пяти ошибок, с которыми вы с высокой вероятностью столкнётесь.
Что такое Puppeteer на AWS Lambda и зачем он вообще нужен
Puppeteer — это библиотека Node.js, которая управляет headless Chromium через Chrome DevTools Protocol. Lambda — это серверлесс-вычисления AWS: вы платите за каждый запуск, масштабирование происходит автоматически, и вам не нужно трогать серверы. Вместе они дают связку для браузерной автоматизации, которая может запускать сотни параллельных задач без единого поднятого EC2-инстанса.
Сценарии использования у команд обычно одни и те же: веб-скрейпинг, генерация скриншотов и PDF, синтетический мониторинг, предрендеринг одностраничных приложений для SEO и автоматизированное UI-тестирование. Проблема всегда одна — размер Chromium по сравнению с лимитами Lambda. Именно поэтому никто не выкладывает в Lambda полный puppeteer (он тащит за собой собственную версию Chromium). Вместо этого используют puppeteer-core без встроенного браузера вместе с оптимизированным под Lambda бинарником Chromium, чаще всего @sparticuz/chromium.
Одна эта замена — puppeteer-core вместо puppeteer — закрывает около 80% проблемы с размером ещё до того, как вы напишете хоть один файл конфигурации деплоя.
Layers, Container Image или ZIP: сначала выберите свой путь
Вот что меня раздражало во время исследования: почти каждый существующий гайд описывает только один способ деплоя. Туториалы по AWS SAM используют Layers. Примеры на CDK идут через Docker. Какой-нибудь пост на Substack показывает сырой ZIP с Chromium-бинарником, лежащим в S3. Никто не ставит их рядом, а значит, первый и главный вопрос — какой метод деплоя подходит именно вам — вообще выпадает.
Давайте это исправим.
| Критерий | Lambda Layers | Container Image (Docker) | Прямая загрузка ZIP |
|---|---|---|---|
| Максимальный размер | 250 МБ после распаковки (суммарно для всех слоёв) | 10 ГБ на образ | 250 МБ после распаковки |
| Сложность деплоя | Средняя (управление ARN слоя) | Выше (Dockerfile + push в ECR) | Самая низкая (zip и upload) |
| Влияние на cold start | Умеренное | Немного выше (более крупный образ дольше загружается) | Умеренное |
| Обновление Chromium | Опубликовать новую версию слоя | Пересобрать образ | Перезагрузить ZIP |
| Лучше всего подходит для | Быстрых прототипов, пользователей Serverless Framework | Продакшн-нагрузок, команд с Docker CI | Простых одноразовых функций |
| Поддержка IaC | SAM, Serverless Framework | CDK, SAM, Terraform | Console, любой IaC |
И лимит 250 МБ, и лимит 10 ГБ взяты напрямую из официальной документации AWS по квотам Lambda — это не число, которое часто менялось, но именно оно заранее определяет всю стратегию деплоя.
Моя практическая эвристика такая: если вы только прототипируете или уже используете Serverless Framework, начинайте с Layers. Если вы выкатываете это в прод и у команды уже есть Docker CI/CD, берите Container Image — потолок в 10 ГБ даёт заметный запас. Если вам нужна всего одна функция для периодических скриншотов, прямая загрузка ZIP — это минимум лишней возни.
Во всех трёх вариантах базовая связка одна и та же: puppeteer-core + @sparticuz/chromium. Меняется способ упаковки, а не набор зависимостей.

Матрица совместимости версий на 2026 год: хватит гадать
Вот что реально отнимает у людей месяцы, а не часы. Самая частая жалоба на Stack Overflow и GitHub issues — не "как это задеплоить", а "почему рабочий деплой молча сломался после обновления npm". Виновник почти всегда один и тот же: несовместимость между @sparticuz/chromium, puppeteer-core и Node.js runtime.
Сначала важное предупреждение: chrome-aws-lambda (оригинальный пакет alixaxel) устарел и больше не поддерживается. На Node 18+ он ломается и не успевает за релизами Chromium. Если в туториале указан этот пакет, можно смело закрывать вкладку — материал устарел. Все актуальные гайды должны ссылаться на @sparticuz/chromium.
Вместо подбора major-версий на глаз используйте эту логику совместимости:
| Компонент | Правило по версии | Что проверить перед деплоем |
|---|---|---|
puppeteer-core | Берите ту версию Puppeteer, которая нужна вашему приложению | Посмотрите, какая версия Chromium поддерживается этим релизом Puppeteer |
@sparticuz/chromium | Его major-версия соответствует major-версии Chromium, а не Puppeteer | Сверьте её с версией Chromium из таблицы поддержки Puppeteer и прочитайте release notes Sparticuz |
| AWS Lambda Node.js runtime | Используйте актуально поддерживаемый runtime Lambda | Прогоняйте тестовый вызов после каждого обновления runtime или пакета |
| Архитектура | npm-пакет содержит x64-бинарники; поддержка arm64 начинается с Chromium v135 через arm64 layer или remote pack | Точно сопоставьте архитектуру Lambda, артефакт слоя/pack и версию Chromium |
Я специально не закрепляю здесь конкретную пару пакетов, потому что @sparticuz/chromium следует циклу релизов Chromium и не использует обычное семантическое версионирование. Начните с официальной страницы Puppeteer Chromium Support, определите major-версию Chromium, которую поддерживает выбранный вами релиз Puppeteer, а затем берите соответствующую major-версию @sparticuz/chromium. После этого обязательно прочитайте release notes Sparticuz — там бывают важные изменения на уровне патчей и нюансы по архитектуре. Не ставьте одинаковый номер major у обоих пакетов просто по совпадению, если это не подтверждено этими двумя источниками.

Как задеплоить Puppeteer на AWS Lambda через Lambda Layers
Lambda Layer позволяет упаковать Chromium отдельно от кода функции, благодаря чему сам обработчик остаётся компактным, а один и тот же Chromium layer можно переиспользовать в нескольких функциях. Это максимально близко к режиму "быстрого старта" в этой теме.
Шаг 1: установите puppeteer-core и пакет -min
Если файлы Chromium лежат в Lambda Layer, держите пакет функции маленьким и используйте @sparticuz/chromium-min. Подставьте вместо плейсхолдеров совместимые версии, которые вы подтвердили выше:
npm install puppeteer-core@$PUPPETEER_VERSION \
@sparticuz/chromium-min@$CHROMIUM_VERSION
Вы ставите puppeteer-core, а не puppeteer, потому что он не скачивает браузер автоматически. Пакет -min даёт только вспомогательные функции для запуска, а слой предоставляет Brotli-сжатые файлы Chromium в /opt/chromium.
Шаг 2: создайте или подключите Chromium Lambda Layer
Используйте архив слоя под нужную архитектуру из официального релиза Sparticuz либо соберите архив из официального репозитория. Для Lambda под x86_64 документированная сборка выглядит так:
git clone --depth=1 https://github.com/sparticuz/chromium.git
cd chromium
make chromium.x64.zip
В результате получится chromium.x64.zip. Загрузите его в S3 и опубликуйте как Lambda Layer с тем runtime и той архитектурой, которые вы реально используете. Для arm64 берите соответствующий arm64-артефакт релиза или target сборки; не подключайте x64-архив к функции arm64.
Если вы используете SAM, подключите ARN слоя прямо в template.yaml:
Resources:
PuppeteerFunction:
Type: AWS::Serverless::Function
Properties:
Layers:
- arn:aws:lambda:us-east-1:XXXXXXXXXXXX:layer:chromium-layer:1
Шаг 3: напишите Lambda Handler
Вот рабочий шаблон handler'а, который открывает URL и возвращает заголовок страницы:
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();
}
};
Обратите внимание на блок finally. Всегда закрывайте браузер именно там — если этого не сделать, тёплые Lambda-окружения начнут накапливать "зомби"-процессы браузера между вызовами, и в итоге вы упрётесь в странные ошибки по памяти, которые вообще не связаны с вашим кодом.
Шаг 4: настройте память, таймаут и архитектуру
Поставьте память минимум 1024 МБ — для чего-то сложнее простого скриншота я бы советовал 1536–2048 МБ. Таймаут задайте не меньше 60 секунд. Архитектуру фиксируйте на x86_64, если только вы не подтвердили поддержку arm64 именно для вашей версии Chromium (это зависит от релиза к релизу).
Шаг 5: задеплойте и проверьте
sam build && sam deploy --guided
Запустите тестовый event, а если что-то пошло не так — сразу смотрите CloudWatch Logs. 90% ошибок из раздела ниже там видны довольно быстро и достаточно понятно.
Как задеплоить Puppeteer на AWS Lambda через Container Images (Docker)
Container images полностью снимают головную боль с лимитом 250 МБ, потому что вместо него вы получаете потолок в 10 ГБ. Это обычно лучший выбор для продакшн-нагрузок, особенно если у команды Docker уже есть в CI-пайплайне.
Шаг 1: создайте Dockerfile
Начните с официального AWS Lambda base image для Node.js, установите зависимости и задайте handler:
FROM public.ecr.aws/lambda/nodejs:20
COPY package*.json ./
RUN npm install --production
COPY . .
CMD ["index.handler"]
В зависимости от вашего пакета Chromium, может понадобиться yum install нескольких shared libraries (об этом подробнее в разделе troubleshooting). @sparticuz/chromium уже включает большую часть нужного, поэтому по сравнению с ручной установкой полноценного Chrome это намного менее болезненно.
Шаг 2: соберите образ и отправьте его в 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
Храните образ в том же регионе, где находится ваша Lambda-функция — кросс-региональная загрузка образа только добавляет лишнюю задержку.
Шаг 3: создайте Lambda-функцию из container image
Укажите вашей функции URI образа из ECR через CLI или CDK и настройте память (1536–2048 МБ) и timeout (60–120 секунд) так же, как это делается при деплое через layer.
Шаг 4: задеплойте и проверьте
Запустите тестовый event и проверьте результат. Главное отличие от Layers: cold start обычно чуть выше из-за более крупной загрузки образа, зато у вас намного больше пространства для зависимостей.
Как задеплоить Puppeteer на AWS Lambda через прямую загрузку ZIP
Это вариант без лишней сложности: никаких layers, никакой сборки Docker. Хорошо подходит для прототипов или одной функции, которой не нужно вырастать в полноценную платформу браузерной автоматизации.
Шаг 1: установите зависимости локально
Для самодостаточного ZIP используйте puppeteer-core + @sparticuz/chromium и зафиксируйте обе версии. Полный пакет содержит сжатые файлы Chromium и распаковывает их в /tmp во время выполнения. @sparticuz/chromium-min используйте только тогда, когда эти файлы приходят отдельно через Lambda Layer или через быстрый remote pack URL; сам пакет -min Brotli-файлы не включает.
Шаг 2: соберите проект и упакуйте функцию в ZIP
npm install --production
zip -r function.zip . -x "*.git*"
Флаг --production здесь важен — dev-зависимости просто съедают ваш бюджет 250 МБ без какой-либо пользы.
Шаг 3: загрузите архив и настройте Lambda
aws lambda update-function-code --function-name my-puppeteer-fn --zip-file fileb://function.zip
Если ваш ZIP больше 50 МБ, через консоль или простую CLI-команду его напрямую уже не загрузить — сначала придётся отправить файл в S3 и затем указать S3 URI. Память, таймаут и архитектуру настраивайте так же, как и для двух предыдущих способов.
Шаг 4: задеплойте и проверьте
Используйте тот же цикл: запуск, проверка, логи. В полном пакете chromium.executablePath() не требует аргумента. В chromium-min передавайте точный путь к каталогу слоя или remote pack URL, например chromium.executablePath("/opt/chromium") для описанной выше схемы слоя. Remote pack добавляет загрузку в первый cold start, поэтому держите его ближе к функции и обязательно проверяйте версию артефакта и архитектуру.
Аргументы для puppeteer.launch(), которые реально работают на Lambda
Это тот самый сниппет, который все копируют, так что давайте сделаем его правильно. В среде исполнения Lambda нет /dev/shm, нет GPU и ограничены права доступа — а значит, стандартный puppeteer.launch(), который отлично работает на ноутбуке, здесь просто... не взлетит.
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,
});
Массив chromium.args из @sparticuz/chromium уже содержит флаги, которые важны для серверлесс-среды: --no-sandbox, --disable-gpu, --disable-dev-shm-usage и другие. В этом и смысл пакета — не собирать флаги руками, а использовать набор, который уже учитывает требования Chromium.

Troubleshooting: 5 ошибок, с которыми сталкивается почти каждый
Ни один найденный мной гайд не включает нормальный раздел по устранению ошибок, и это странно, потому что ошибки — это, скорее всего, именно причина, по которой вы открыли эту статью.
"Failed to launch the browser process"
Причина: не хватает shared libraries (libnss3.so, libatk и т. п.) либо указан неправильный executablePath.
Решение: @sparticuz/chromium поставляет большую часть нужных зависимостей, поэтому его и рекомендуют вместо самодельной сборки Chromium. Для Docker-деплоя, если ошибка остаётся, явно установите недостающие библиотеки через yum install в Dockerfile.
"Unzipped size must be smaller than 262144000 bytes"
Причина: вы поставили полный пакет puppeteer, который тащит собственную загрузку Chromium (~400 МБ).
Решение: переключитесь на puppeteer-core + @sparticuz/chromium. Если вам действительно нужно больше места, переходите на вариант с Container Image и лимитом 10 ГБ.
"Browser disconnected" или таймаут на browser.newPage()
Причина: недостаточно памяти в Lambda, либо в launch-аргументах отсутствуют флаги вроде --disable-gpu.
Решение: выделите минимум 1024 МБ памяти (я бы рекомендовал больше — см. бенчмарки ниже) и убедитесь, что передаёте chromium.args, а не урезанный кастомный список.
Рабочий код ломается после обновления Lambda runtime
Причина: AWS периодически патчит базовый runtime, и это может менять версии shared libraries или патч-версии Node.js под вами.
Решение: явно закрепите версию @sparticuz/chromium, закрепите версию Node runtime в конфиге функции и — это как раз тот шаг, который многие пропускают — тестируйте после каждого анонса обновления runtime от AWS, а не только когда что-то уже сломалось.
"Protocol error: Connection closed" примерно через 30 секунд
Причина: timeout Lambda меньше, чем нужно странице на загрузку и отрисовку.
Решение: увеличьте timeout до 60–120 секунд, явно задайте page.setDefaultNavigationTimeout(), а если не нужно ждать полного завершения всех сетевых запросов, замените waitUntil: 'networkidle0' на waitUntil: 'domcontentloaded'.
Продакшн-укрепление: память, cold starts и стоимость
Большинство гайдов советуют просто "увеличить память" и на этом заканчивают. Это бесполезный совет — ниже то, что реально меняется при росте объёма памяти.
Память vs. производительность
Lambda выделяет CPU пропорционально памяти, и именно это часто сбивает людей с толку. Больше памяти — это не просто больше RAM, а ещё и более быстрый CPU, а значит, и более быстрое рендеринг Chromium. На практике команды, которые гоняли бенчмарки Puppeteer, обычно получают заметно более быстрые выполнения при росте с 512 МБ до диапазона 1536–2048 МБ, хотя точные цифры сильно зависят от страниц, которые вы рендерите. Вместо того чтобы цитировать таблицу бенчмарков, которая устареет раньше, чем вы её прочитаете, запустите собственный тест на 512 МБ, 1024 МБ, 1536 МБ и 2048 МБ на ваших целевых страницах — это займёт минут десять и точно покажет, где у вас оптимум по цене и скорости.
Provisioned Concurrency для cold starts
Если у вас чувствительный к задержке сценарий — например, синтетический мониторинг или API для скриншотов в реальном времени — cold starts становятся врагом. Provisioned Concurrency держит заданное число окружений тёплыми и готовыми к работе, убирая задержку старта, но вы платите за этот простой резерв. Это оправдано именно тогда, когда важнее задержка, а не максимальная экономия.
arm64 (Graviton) ради экономии
Lambda-функции на Graviton обычно примерно на 20% дешевле, чем x86_64-аналоги. Но есть нюанс: поддержка arm64 у @sparticuz/chromium исторически была ограниченнее, чем у x86_64, поэтому перед переходом на Graviton в проде обязательно проверьте совместимость именно для вашей зафиксированной версии.
VPC против no-VPC
Раньше размещение функции в VPC заметно увеличивало cold-start задержку; AWS за последние годы сильно сократил этот разрыв, но он всё равно не стал нулевым. Помещайте функцию в VPC только если ей действительно нужен доступ к приватным ресурсам вроде RDS или ElastiCache. Если нет — не усложняйте.
Когда пора уходить с Lambda
Если ваши browser-задачи регулярно длятся дольше 15 минут, требуют больше 10 ГБ памяти или нуждаются в постоянных браузерных сессиях между запросами, Lambda уже начинает мешать. Именно для этого и существует ECS Fargate: длительные задачи, настраиваемые ресурсы, оплата по секундам. Lambda отлично подходит для коротких, всплесковых и хорошо параллелящихся задач с браузером, но это плохой инструмент, когда нагрузка больше похожа на постоянный сервис.
Когда Puppeteer на Lambda — не лучший выбор
Есть важная мысль, которую стоит честно принять: большая часть разработчиков, которые приходят к гайдам про "Puppeteer + Lambda", на самом деле решают задачу извлечения данных, а не браузерной автоматизации. Если вам нужны структурированные данные со страниц — список товаров, контакты, контент страницы — вся упаковка Chromium, фиксация версий и управление слоями выше становятся лишней сложностью.
Оставайтесь на Lambda + Puppeteer, если вам нужен настоящий контроль над браузером: взаимодействие с формами, генерация скриншотов/PDF, синтетический мониторинг или browser-based testing, где вы действительно программно управляете DOM.
Рассмотрите scraping API, если ваша конечная цель — получить структурированный JSON со страницы, а не управлять браузерной сессией самостоятельно. Thunderbit Open API обрабатывает JS-рендеринг, антибот-защиту и CAPTCHA через один HTTP-запрос — POST /extract со схемой JSON даёт вам структурированные данные, а POST /distill — чистый Markdown. Есть и MCP-сервер (thunderbit_extract, thunderbit_distill), если вы строите AI-агента, которому нужно подтягивать данные прямо во время рабочего процесса без запуска собственного браузера.
| Фактор | Lambda + Puppeteer (самостоятельно) | Extraction API (например, Thunderbit) |
|---|---|---|
| Время на запуск | Часы (упаковка, слои, отладка) | Минуты (API-ключ + HTTP-запрос) |
| Поддержка | Постоянно ваша (фиксация версий, обновления runtime) | На стороне провайдера |
| Антибот-защита | Вручную (stealth-плагины, прокси) | Встроена |
| Формат результата | Сырые HTML/скриншоты, которые нужно парсить самому | Структурированный JSON по схеме |
| Лучше всего для | Полной browser automation, тестирования, кастомных потоков | Извлечения данных, скрейпинга, ingestion контента |
Скажу прямо: если вы часами отлаживаете Chromium-бинарники только ради того, чтобы вытащить JSON из страниц товаров, это признак того, что вы решаете не ту задачу. Оставляйте DIY-Lambda-путь для случаев, когда вам действительно нужно управлять браузером — для извлечения данных есть более прямой вариант. Если вы выбираете между этими подходами для конкретного проекта, наш гайд по AI web scraper подробнее разбирает рынок, а расширение Thunderbit для Chrome стоит посмотреть, если хотите сначала протестировать подход "сначала извлечение", а уже потом выбирать окончательную архитектуру.
Итоги
Три способа деплоя, одна повторяющаяся мысль: фиксируйте версии, давайте Chromium достаточно памяти и выбирайте метод деплоя под реальные ограничения, а не под первый попавшийся туториал. Layers — для быстрой итерации, Container Images — для продакшн-масштаба, ZIP — для простых разовых задач. А если на самом деле вы не автоматизируете браузер, а извлекаете данные, возможно, вам выгоднее сразу использовать специализированный API для extraction и вообще не тратить время на упаковку.
FAQ
Можно ли запускать Puppeteer на AWS Lambda в 2026 году?
Да — если использовать puppeteer-core вместе с @sparticuz/chromium и деплоить через Layers, Container Image или прямой ZIP. Полный пакет puppeteer и устаревший chrome-aws-lambda уже не работают стабильно на актуальных runtime Lambda.
Какой максимальный размер пакета для AWS Lambda? 250 МБ после распаковки для Layers и ZIP-деплоев; 10 ГБ для Container Image, согласно квотам AWS Lambda.
Поддерживается ли chrome-aws-lambda до сих пор?
Нет. Оригинальный пакет chrome-aws-lambda от alixaxel устарел и ломается на Node 18+. Используйте @sparticuz/chromium — сейчас это поддерживаемый стандарт.
Сколько памяти нужно Puppeteer на AWS Lambda? 1024 МБ — практический минимум; комфортная производительность обычно начинается в диапазоне 1536–2048 МБ. Ниже 1024 МБ выполнение заметно замедляется, потому что Lambda привязывает CPU к объёму памяти.
Как уменьшить cold start для Puppeteer на Lambda? Выделите больше памяти, чтобы получить и больше CPU, рассмотрите Provisioned Concurrency, если задержка критична, и держите пакет деплоя максимально компактным — каждая лишняя зависимость увеличивает время холодного старта.


