Chromium はだいたい 280MB あります。一方で、AWS Lambda の展開後パッケージ上限は 250MB です。npm install puppeteer をそのままして Lambda にデプロイしようとして詰まったことがあるなら、この計算がどうなるかはもう分かりますよね。つまり、無理です。
私はこれまで、午前2時に「Failed to launch the browser process」エラーの原因を延々と追いかけてきたので、このテーマは「1つの方法だけを見せて終わり」ではなく、ちゃんと比較する価値があると感じています。そこで本記事では、Layers・コンテナイメージ・直接 ZIP アップロードの3方式を横並びで比べ、2026年時点で有効なバージョン互換マトリクスと、実際によく踏む5つのエラーに絞ったトラブルシューティングまでまとめます。
AWS Lambda 上で Puppeteer を使うとは? なぜわざわざやるのか
Puppeteer は、Chrome DevTools Protocol を使ってヘッドレス Chromium を操作する Node.js ライブラリです。Lambda は AWS のサーバーレス実行環境なので、実行回数に応じて課金され、自動でスケールし、サーバー管理も不要です。この2つを組み合わせると、EC2 を1台も立てずに、数百並列のブラウザ自動化を回せる構成が作れます。
用途はチームをまたいでもかなり共通していて、ウェブスクレイピング、スクリーンショットや PDF の生成、シンセティックモニタリング、SEO 向けのシングルページアプリ事前レンダリング、UI テストの自動化などが代表例です。問題はいつも同じで、前述の Chromium のサイズと Lambda のパッケージ制限です。そのため、Chromium を同梱したフル版の puppeteer をそのまま Lambda に載せる人はいません。代わりに、ブラウザを同梱しない puppeteer-core と、Lambda 向けに最適化された Chromium バイナリ、一般的には @sparticuz/chromium を組み合わせます。
この置き換え、つまり puppeteer ではなく puppeteer-core を使うだけで、デプロイ設定を書く前にサイズ問題の8割は片づきます。
Layers・コンテナイメージ・ZIP:まずは進む道を決める
調べていて気になったのは、既存のガイドのほとんどが、たった1つのデプロイ方法しか扱っていないことです。AWS SAM のチュートリアルは Layers、CDK のサンプルは Docker、Substack の記事では S3 上の Chromium バイナリを使った生 ZIP。並べて比較してくれるものがなく、本来いちばん先に決めるべき「自分のケースに合う方法はどれか」が抜け落ちがちです。
なので、ここで整理します。
| 比較項目 | Lambda Layers | コンテナイメージ(Docker) | 直接 ZIP アップロード |
|---|---|---|---|
| 最大サイズ | 250MB(展開後、全 Layer 合算) | 10GB イメージ | 250MB(展開後) |
| デプロイの手間 | 中程度(Layer ARN の管理が必要) | やや高い(Dockerfile + ECR への push) | 最小(zip にしてアップロード) |
| コールドスタートへの影響 | 中程度 | やや大きい(イメージ取得が増える) | 中程度 |
| Chromium 更新フロー | Layer を再公開 | イメージを再ビルド | ZIP を再アップロード |
| 向いている用途 | 試作、Serverless Framework 利用者 | 本番運用、Docker CI があるチーム | 単発のシンプルな関数 |
| IaC 対応 | SAM、Serverless Framework | CDK、SAM、Terraform | コンソール、任意の IaC |
250MB と 10GB の制限は、どちらも AWS 公式の Lambda クォータ文書 にある数値です。頻繁に変わるものではありませんが、デプロイ戦略を最初に決めるうえで最も重要な制約です。
ざっくりした目安としては、試作段階か、すでに Serverless Framework を使っているなら Layers から始めるのが無難です。本番投入で、チームがすでに Docker ベースの CI/CD を持っているなら、コンテナイメージが有力です。10GB の余裕があるので、かなり自由度が上がります。たまにスクリーンショットを撮るだけの関数なら、いちばん手間が少ないのは直接 ZIP です。
どの方法でも、裏側で使う依存関係の組み合わせは同じです。puppeteer-core + @sparticuz/chromium。違うのは、その組み合わせをどう梱包して配布するかであって、何を使うかではありません。

2026年版のバージョン互換マトリクス(もう勘でやらない)
ここが、時間ではなく数か月単位で人を消耗させる部分です。Stack Overflow や GitHub Issues でいちばん目立つ不満は、「どうやってデプロイするか」ではなく、「npm を更新しただけで、動いていたものが静かに壊れたのはなぜか」です。原因の多くは、@sparticuz/chromium、puppeteer-core、Node.js ランタイムの不一致です。
まず重要な注意点です。chrome-aws-lambda(元の alixaxel パッケージ)は非推奨です。 Node 18 以降で壊れており、Chromium の更新にも追随できていません。この名前が出てくる古いチュートリアルを見つけたら、閉じて大丈夫です。今のガイドはすべて、代わりに @sparticuz/chromium を案内しているはずです。
パッケージのメジャーバージョンを目視で合わせるのではなく、次のルールで判断してください。
| コンポーネント | バージョンの考え方 | デプロイ前に確認すること |
|---|---|---|
puppeteer-core | アプリが必要とする Puppeteer のバージョンを選ぶ | その Puppeteer リリースが対応する Chromium ビルドを確認する |
@sparticuz/chromium | メジャーバージョンは Puppeteer ではなく Chromium のメジャーに追従する | Puppeteer の対応表で Chromium ビルドを照合し、Sparticuz のリリースノートも読む |
| AWS Lambda の Node.js ランタイム | 現在サポートされているランタイムを使う | ランタイムまたはパッケージ更新のたびに実行テストを行う |
| アーキテクチャ | npm パッケージは x64 バイナリを含む。arm64 対応は Chromium v135 以降で arm64 Layer または remote pack を使って始まる | Lambda のアーキテクチャ、Layer/pack の成果物、Chromium のバージョンを正確に合わせる |
ここで特定のパッケージの組み合わせを固定で書かないのは、@sparticuz/chromium が Chromium のリリースサイクルに追従していて、通常のセマンティックバージョニングの感覚では扱えないからです。まず Puppeteer の公式 Chromium 対応ページ で、使う Puppeteer リリースが対応している Chromium のメジャー番号を確認します。次に、そのメジャーに対応する @sparticuz/chromium を選びます。最後に Sparticuz のリリースノート で、パッチレベルの破壊的変更やアーキテクチャ情報を確認してください。2つのソースで対応関係が確認できていない限り、両方のパッケージを同じメジャー番号だからといって安易に入れないでください。

Lambda Layers を使って AWS Lambda に Puppeteer をデプロイする方法
Lambda Layer を使うと、Chromium を関数コードとは別にパッケージ化できます。そのため、実際のハンドラーは小さく保てますし、同じ Chromium Layer を複数の関数で使い回せます。この領域では、いちばん「すぐ始めやすい」方法に近いです。
Step 1: puppeteer-core と -min パッケージをインストールする
Chromium のファイルを Lambda Layer に置く場合、関数パッケージは小さいままにしたいので、@sparticuz/chromium-min を使います。先ほど確認した互換バージョンを、プレースホルダーに置き換えてください。
npm install puppeteer-core@$PUPPETEER_VERSION \
@sparticuz/chromium-min@$CHROMIUM_VERSION
インストールしているのは puppeteer ではなく puppeteer-core です。自動でブラウザをダウンロードしないからです。-min パッケージは起動用のヘルパーを提供し、Layer 側が /opt/chromium 配下に Brotli 圧縮された Chromium ファイルを供給します。
Step 2: Chromium の Lambda Layer を作成または参照する
公式 Sparticuz リリース に付属するアーキテクチャ別の Layer アーカイブを使うか、公式リポジトリからアーカイブをビルドします。x86_64 Lambda 向けの文書化されたビルドは次の通りです。
git clone --depth=1 https://github.com/sparticuz/chromium.git
cd chromium
make chromium.x64.zip
これで chromium.x64.zip ができます。これを S3 にアップロードし、実際に使うランタイムとアーキテクチャに合わせて Lambda Layer として公開してください。arm64 なら、対応する arm64 のリリース成果物またはビルドターゲットを使い、x64 のアーカイブを arm64 関数に付けないでください。
SAM を使う場合は、template.yaml で Layer ARN を直接指定します。
Resources:
PuppeteerFunction:
Type: AWS::Serverless::Function
Properties:
Layers:
- arn:aws:lambda:us-east-1:XXXXXXXXXXXX:layer:chromium-layer:1
Step 3: Lambda ハンドラーを書く
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 環境にゾンビブラウザが残り続け、あとで関係ないはずのメモリエラーに悩まされることになります。
Step 4: メモリ・タイムアウト・アーキテクチャを設定する
メモリは少なくとも 1024MB にしてください。スクリーンショット程度の処理でも、実運用なら 1536〜2048MB をおすすめします。タイムアウトは最低でも 60 秒に設定します。アーキテクチャは、arm64 対応をその Chromium バージョンで明確に確認できていない限り、x86_64 に固定しておくのが無難です(この点はリリースごとに変わります)。
Step 5: デプロイしてテストする
sam build && sam deploy --guided
テストイベントで実行し、少しでもおかしければすぐ CloudWatch Logs を確認してください。下のトラブルシューティングに出てくるエラーの90%は、ログにそのまま現れます。
コンテナイメージ(Docker)を使って AWS Lambda に Puppeteer をデプロイする方法
コンテナイメージなら、250MB 問題を丸ごと回避して、10GB の上限を使えます。特に、本番ワークロードで、チームがすでに CI に Docker を使っているなら、こちらのほうが基本的に有利です。
Step 1: Dockerfile を作成する
Node.js 向けの公式 AWS Lambda ベースイメージ を起点にし、依存関係を入れ、ハンドラーを指定します。
FROM public.ecr.aws/lambda/nodejs:20
COPY package*.json ./
RUN npm install --production
COPY . .
CMD ["index.handler"]
Chromium パッケージによっては、いくつかの共有ライブラリを yum install する必要があります(詳細はトラブルシューティングで説明します)。@sparticuz/chromium は必要なものの大半を同梱しているので、Chrome を手動でフルインストールするよりははるかに楽です。
Step 2: Amazon ECR にビルドして push する
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 関数と同じリージョンに置いてください。リージョンをまたぐイメージ取得は、不要なレイテンシを増やします。
Step 3: コンテナイメージから Lambda 関数を作成する
CLI または CDK で、関数を ECR のイメージ URI に向けます。メモリ(1536〜2048MB)とタイムアウト(60〜120秒)は、Layer ベースのデプロイと同じ考え方で設定してください。
Step 4: デプロイしてテストする
テストイベントで実行し、出力を確認します。Layers と比べたときの主なトレードオフは、イメージ取得が大きくなるぶんコールドスタートが少し増えることですが、その代わり依存関係にかなり余裕が持てます。
直接 ZIP アップロードで AWS Lambda に Puppeteer をデプロイする方法
これは最小構成の選択肢です。Layer 管理もなく、Docker ビルドもありません。試作や、ブラウザ自動化基盤まで大きく育てる予定のない単発関数に向いています。
Step 1: ローカルで依存関係を入れる
自己完結型の ZIP にするなら、puppeteer-core + @sparticuz/chromium を使い、両方のバージョンを固定してください。フルパッケージには圧縮済み Chromium ファイルが含まれており、実行時に /tmp へ展開されます。@sparticuz/chromium-min を使うのは、それらのファイルを別途 Lambda Layer か高速な remote pack URL で供給する場合だけです。-min パッケージ自体には Brotli ファイルは含まれていません。
Step 2: 関数をまとめて ZIP 化する
npm install --production
zip -r function.zip . -x "*.git*"
ここでは --production が重要です。開発用依存関係は、何の意味もなく 250MB の枠を食いつぶします。
Step 3: Lambda 関数をアップロードして設定する
aws lambda update-function-code --function-name my-puppeteer-fn --zip-file fileb://function.zip
ZIP が 50MB を超える場合、コンソールや簡単な CLI 呼び出しから直接アップロードすることはできません。先に S3 に置いて、その S3 URI を参照する必要があります。メモリ、タイムアウト、アーキテクチャの設定は、前の2つの方法と同じです。
Step 4: デプロイしてテストする
これまでと同じく、実行してログを見る流れで確認します。フルパッケージでは、chromium.executablePath() に引数は不要です。chromium-min の場合は、Layer の配置や remote pack URL に合わせて正確なパスを渡します。たとえば上の Layer 構成なら chromium.executablePath("/opt/chromium") です。remote pack は初回コールドスタート時にダウンロード処理が追加されるので、関数の近くに置き、成果物のバージョンとアーキテクチャを必ず確認してください。
Lambda で実際に動く puppeteer.launch() の引数
ここは誰もがコピペする部分なので、正しくしておきましょう。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,
});
@sparticuz/chromium の chromium.args には、サーバーレス環境で必要なフラグがすでに入っています。--no-sandbox、--disable-gpu、--disable-dev-shm-usage などです。自前でフラグを並べるのではなく、このパッケージを使う意味はまさにそこにあります。Chromium の要件変化に追随してくれるので、自分で全部管理しなくて済みます。

トラブルシューティング:誰もが踏みがちな5つのエラー
私が見つけた既存ガイドのどれにも、きちんとしたトラブルシューティング節がありませんでした。でも、エラーが起きるからこそこの記事を読んでいるはずなので、それは少し不思議です。
「Failed to launch the browser process」
原因: 共有ライブラリ(libnss3.so、libatk など)が足りない、または executablePath が間違っている。
対処: @sparticuz/chromium は必要な依存関係の大半を同梱しています。自前で Chromium バイナリを組み立てるより、このパッケージが推奨される理由がここにあります。Docker デプロイでまだ出るなら、Dockerfile に不足ライブラリを yum install で明示的に追加してください。
「Unzipped size must be smaller than 262144000 bytes」
原因: フル版の puppeteer を入れてしまい、Chromium のダウンロード分(約400MB)まで含まれている。
対処: puppeteer-core + @sparticuz/chromium に切り替えてください。どうしても容量が足りないなら、コンテナイメージ方式に移行して 10GB の上限を使うほうが現実的です。
「Browser disconnected」または browser.newPage() でタイムアウトする
原因: Lambda のメモリが足りない、または --disable-gpu のようなフラグが起動引数に入っていない。
対処: メモリを少なくとも 1024MB に上げます(できればもう少し上。下のベンチマークの考え方を参照してください)。そして、短い自作の引数リストではなく、chromium.args を渡しているか確認します。
動いていたコードが Lambda ランタイム更新後に壊れる
原因: AWS が基盤ランタイムを定期的にパッチしており、それによって共有ライブラリや Node.js のパッチバージョンが変わることがある。
対処: @sparticuz/chromium のバージョンを明示的に固定し、Lambda 関数設定で Node ランタイムも固定します。そして、ここが見落とされがちですが、AWS のランタイム更新告知のたびに再テストしてください。壊れてからでは遅いです。
約30秒後に「Protocol error: Connection closed」になる
原因: ページの読み込みと描画が終わる前に Lambda のタイムアウトが来ている。
対処: タイムアウトを 60〜120 秒に延ばし、page.setDefaultNavigationTimeout() を明示的に設定し、すべてのネットワーク通信を待つ必要がないなら waitUntil: 'networkidle0' の代わりに waitUntil: 'domcontentloaded' を使ってください。
本番向けの強化:メモリ、コールドスタート、コスト
多くのガイドは「メモリを増やしましょう」で終わります。でも、それだけでは実務では足りません。メモリを増やすと、実際に何がどう変わるのかを見ていきます。
メモリとパフォーマンスの関係
Lambda はメモリに比例して CPU も割り当てます。ここが見落とされやすいポイントです。つまり、メモリ増加は単に「使える RAM が増える」だけではなく、CPU も速くなり、そのまま Chromium の描画速度に効きます。実務では、512MB から 1536〜2048MB に上げるとかなり速くなる、というベンチマーク結果がよくあります。ただし、実際の数値は描画対象のページ次第で大きく変わります。ここで特定のベンチマーク表を引用しても、読んでいる頃には古くなっているでしょう。だからこそ、自分の対象ページに対して 512MB、1024MB、1536MB、2048MB で実測するのが一番です。10分もあれば、費用対効果の最適点がはっきり見えます。
コールドスタート対策としての Provisioned Concurrency
レイテンシが重要なシステム、たとえばシンセティックモニタリングやリアルタイムのスクリーンショット API では、コールドスタートは敵です。Provisioned Concurrency を使うと、一定数の実行環境を温かい状態で待機させられるため、コールドスタートのペナルティをなくせます。その代わり、待機している分のコストはかかります。純粋なコスト効率よりもレイテンシのほうが重要な場合には、十分価値があります。
arm64(Graviton)でコストを下げる
Graviton ベースの Lambda 関数は、x86_64 と比べておよそ20%安く動かせます。ただし、@sparticuz/chromium の arm64 対応は歴史的に x86_64 より限定的だったので、本番で Graviton を採用する前に、固定したバージョンで必ず明示的に確認してください。
VPC と非 VPC
VPC 内に関数を置くと、以前はコールドスタートの遅延がかなり増えました。最近は AWS がかなり改善していますが、ゼロではありません。RDS や ElastiCache のようなプライベートリソースにアクセスする必要が本当にある場合だけ VPC に入れてください。そうでないなら、外しておくほうが軽快です。
Lambda から完全に離れるべきタイミング
ブラウザ処理が15分を超えがち、10GB を超えるメモリが必要、あるいはリクエストをまたいでブラウザセッションを持続させたいなら、その時点で Lambda は不向きです。そういう用途は ECS Fargate の得意分野です。長時間動く、リソースを柔軟に設定できる、秒単位課金の実行基盤だからです。Lambda は、短時間・バースト的・並列化しやすいブラウザ処理には最高ですが、常時稼働サービスのようなワークロードには向いていません。
Puppeteer を Lambda に載せるのが間違っているケース
ここは正直に考える価値があります。Puppeteer + Lambda の記事にたどり着く人の多くは、本当はブラウザ自動化ではなく、データ抽出の問題を解きたいだけです。必要なのが商品一覧、連絡先、ページ本文のような構造化データなら、上で説明した Chromium の梱包、バージョン固定、Layer 管理は、そもそも背負う必要のないオーバーヘッドかもしれません。
Lambda + Puppeteer を選ぶべきケース は、本当にブラウザを操作する必要があるときです。たとえば、独自フォームとのやり取り、スクリーンショットや PDF の生成、シンセティックモニタリング、DOM をプログラム的に操作するブラウザベースのテストなどです。
スクレイピング API を検討すべきケース は、目的がブラウザセッションではなく、Web ページから構造化 JSON を取り出すことのときです。Thunderbit の Open API なら、JS レンダリング、ボット対策、CAPTCHA を 1回の HTTP リクエストの裏側で処理できます。POST /extract に JSON Schema を送れば構造化データが得られ、POST /distill ならきれいな Markdown が返ってきます。さらに、ワークフローの途中でブラウザを立てずにデータを取りたい AI エージェント向けに、MCP サーバー(thunderbit_extract、thunderbit_distill)もあります。
| 比較項目 | Lambda + Puppeteer(自前構築) | 抽出 API(例: Thunderbit) |
|---|---|---|
| セットアップ時間 | 数時間(梱包、Layer、デバッグ) | 数分(API キー + HTTP 呼び出し) |
| 保守 | 継続的に必要(バージョン固定、ランタイム更新対応) | 提供側が対応 |
| ボット対策 | 手動対応(stealth プラグイン、プロキシ) | 標準搭載 |
| 出力形式 | 生の HTML / スクリーンショットを自分で解析 | Schema から構造化 JSON を取得 |
| 向いている用途 | フルブラウザ自動化、テスト、独自フロー | データ抽出、スクレイピング、コンテンツ取り込み |
はっきり言うと、商品ページから JSON を抜きたいだけなのに Chromium のバイナリで何時間もハマっているなら、そもそも問題設定が違う可能性が高いです。ブラウザを本当に操作する必要があるときだけ、自前の Lambda 方式を使ってください。抽出が目的なら、もっと直接的な方法があります。特定のプロジェクトで迷っているなら、AI web scraper のガイド で全体像を詳しく確認できますし、まず抽出ファーストの方法を試したいなら Thunderbit Chrome Extension もおすすめです。
まとめ
デプロイ方法は3つありますが、共通するテーマはひとつです。バージョンは固定する、Chromium には十分なメモリを与える、そして最初に見つけたチュートリアルではなく、自分の制約に合う方法を選ぶこと。試行錯誤が早いのは Layers、本番規模ならコンテナイメージ、単発なら ZIP。もし本当にやりたいことがブラウザ自動化ではなくデータ抽出なら、専用の抽出 API を使うことで、面倒なパッケージング問題ごと回避できるかもしれません。
よくある質問
2026年でも AWS Lambda で Puppeteer は動きますか?
はい。puppeteer-core と @sparticuz/chromium を組み合わせ、Layers、コンテナイメージ、または直接 ZIP でデプロイすれば動きます。フル版の puppeteer と、非推奨の chrome-aws-lambda は、現在の Lambda ランタイムでは安定して動きません。
AWS Lambda の最大パッケージサイズは? Layers と ZIP デプロイは展開後 250MB、コンテナイメージは 10GB です。これは AWS の Lambda クォータ に基づきます。
chrome-aws-lambda はまだ保守されていますか?
いいえ。元の chrome-aws-lambda パッケージ(alixaxel によるもの)は非推奨で、Node 18 以降で壊れます。今は @sparticuz/chromium を使ってください。現時点での実質的な標準です。
Puppeteer に AWS Lambda ではどれくらいメモリが必要ですか? 実用上の最低ラインは 1024MB です。快適に動かしたいなら 1536〜2048MB が目安です。1024MB を下回ると、Lambda がメモリに応じて CPU も割り当てるため、かなり遅く感じるはずです。
Lambda 上の Puppeteer のコールドスタートを減らすには? メモリを増やす(CPU も増える)、レイテンシが重要なら Provisioned Concurrency を検討する、そしてデプロイパッケージをできるだけ軽く保つことです。依存関係が増えるほど、コールドスタートも伸びます。


