公開 文責 Webaff 編集部
Netlifyのデプロイ失敗と本番チェックの誤検知を切り分けた記録
- Netlify
- 運用
- Astro
2026-09-23、このブログ(Astro + Netlify)で、コードに問題がないのにデプロイと CI が立て続けに失敗しました。原因は Netlify 側の一時的な問題で、最終的に修正したのは CI のチェックだけです。ただ、切り分けの途中で「どこを見れば分かるか」「どこは分からないか」がはっきりしたので、手順として残します。
先に断っておくと、Netlify 内部エラーの根本原因までは分かりません。後半の「キャッシュ説」も仮説です。一方で、exit code: 4 の正体は、ダッシュボードのログで確認できました。
起きたこと
同じ日のうちに、性質の違う失敗が4つ重なりました。
| 順番 | 場所 | 表示 | 実際の原因 |
|---|---|---|---|
| 1 | PR のプレビュー | Failed to fetch environment variables(0秒で終了) |
Netlify 側の一時的な失敗 |
| 2 | PR のプレビュー(再ビルド) | 32秒後に exit code: 4 |
このときのログは未確認。同じ表示の別の例で内部エラーと確認(後述) |
| 3 | main の本番デプロイ | Build failed due to an internal system error |
Netlify の内部エラー |
| 4 | GitHub Actions の live ジョブ |
webaff-blog.netlify.app が HTTP 500 |
3 の直後の一時的な 500(仮説) |
どれも手元では再現しませんでした。npm run build は通り、同じコミットのブランチデプロイも成功していました。手元で再現しない、同じコミットが別の場所で通る、の2点が揃ったら、コードより環境を疑うという判断の目安になりました。
ログはどこまで取れるか
最初にやったのは、Netlify CLI でデプロイの状態を取ることです。
netlify api getDeploy --data '{"deploy_id":"<デプロイID>"}'
返ってくるのは state と error_message までです。Failed during stage 'building site': Build script returned non-zero exit code: 4 のようにどの段階で落ちたかは分かりますが、ビルドログの本文は API では取れません(getSiteBuild も同様)。exit code: 4 がどのコマンドの終了コードかは、この時点では分かりませんでした。
本番デプロイのほうは、ダッシュボードでログを開いて確認できました。段階の表示は次のとおりです。
- Building: 成功
- Deploying: 失敗(
Internal error deploying)
ビルドは通っていて、成果物を配信に載せる段階で失敗しています。つまりコードの問題ではありません。段階別の表示が読めれば、切り分けはかなり進みます。
その後、別の PR のプレビューでまた exit code: 4 が出たときにダッシュボードのログを開くと、本番と同じ Build failed due to an internal system error: Build script returned non-zero exit code と Internal error deploying が並んでいました。API の error_message に出る exit code: 4 は、コマンドの失敗ではなく、この Netlify 内部エラーの表示です。手元のビルドとブランチデプロイが通る場合、exit code: 4 だけで自分のコードを疑う必要はありません。
再ビルドで直った
本番は同じ main のコミットで再ビルドしました。
netlify api createSiteBuild --data '{"site_id":"<サイトID>"}'
結果は ready で、/data/ に変更が反映されていることも本番の HTML で確認しました。PR のプレビューは、空コミットを push して再ビルドさせると通りました。履歴には残りますが、squash マージなら本流には入りません。
live ジョブだけが落ち続けた
本番が復旧しても、Actions の live ジョブは再実行後も HTTP 500 で落ちました。このジョブは本番 URL とは別に、webaff-blog.netlify.app が到達できることも確認します。
手元の curl はずっと 200 でした。ヘッダやユーザーエージェントを変えても再現しません。Actions では 3 回連続で 500、少し待って再実行すると成功しました。
疑ったのはキャッシュです。このサイトのトップページは SSR で、CDN に次の設定でキャッシュさせています。
Netlify-CDN-Cache-Control: public, durable, s-maxage=300, stale-while-revalidate=86400
デプロイが失敗していた間に、Actions の実行元に近い CDN ノードが 500 を返し、それが残った、という説明ができます。ただし 500 の中身を見ていないので、仮説のままです。場所によって結果が違った点だけは事実です。
直したこと
原因が一時的なものなら、チェック側で待てばよいはずです。live ジョブの本番確認には、期待状態になるまで再確認する仕組みがすでにありました。エイリアスの確認だけ、それがありませんでした。
- 5xx のときだけ、既存の待機設定(既定で最大5分・20秒間隔)で再確認する
- 4xx と 3xx は再試行しない(本当の設定ミスを隠さない)
動作は、500 を2回返してから 200 を返すローカルサーバーを立てて確認しました。2回の警告のあとに OK になります。本番に対する実行も OK でした。
この件で決めたこと
- 手元で再現せず、同じコミットが別の場所で通るなら、コードを直す前にデプロイの段階別表示を見る
- Netlify の API ではエラーメッセージまでしか取れないので、ログ本文はダッシュボードで見る(
exit code: 4は内部エラーの表示だった) - CI のチェックは、デプロイ直後の一時的な失敗を許容する作りにする(本当の失敗は待ち切ってから落ちる)
なお、独自ドメインの接続で同じ Netlify を扱った記録はNetlifyに独自ドメインを接続する手順、SSR ページを CDN に載せる前提になっている PV 集計の設計はNetlify BlobsでPVを数える設計とコスト対策にあります。
関連記事
Netlify BlobsでPVを数える設計とコスト対策
静的サイトの人気記事順を自前のPVで並べるために、Edge FunctionとNetlify Blobsで閲覧数を数えた記録。書き込み上限、毎時の集約、二重起動の防ぎ方、止まったときの気づき方まで。
- Netlify
- Astro
- 運用
日本語Webフォントを自己ホストして転送量を削る手順
Google Fontsから日本語フォントを読むとトップページで52リクエスト553KiBになっていた。サイトで使う文字だけのサブセットを自己ホストして3リクエスト396KiBにした実測と、増えたコストの話。
- Astro
- Netlify
- 表示速度
Astroブログでads.txtを設置して確認する手順
Astroのpublicディレクトリにads.txtを置いて本番で応答を確認した記録です。設置できても審査は別問題という実例と、404やHTMLが返る場合の切り分けをまとめます。
- AdSense
- Astro
- Netlify