PHP 緊急アップデートは、たいてい前触れなく出ます。php2shellと呼ばれた CVE-2026-109618・109619 のように、PHP の修正版(8.5.12・8.4.27・8.3.36・8.2.35)が公開されてから、本番の全コンテナに当て終えるまでの数時間が緊急パッチ対応の勝負どころです。このとき時間を食うのは、PHP の設定を変えることでも依存を入れ替えることでもありません。新しいベースイメージでアプリのイメージをビルドし直し、それを全ノードへ配る工程です。本記事は、次の緊急アップデートで慌てないために、このビルドと配布を前もって短くしておく基盤を、Docker と BuildKit の公式ドキュメントで裏を取って整理します。

RACE TO PATCH緊急対応の流れ

修正版が出たら数分で当てる形を前もって作る

  1. 事前

    メンテ画面を即出せる形に

    環境変数かフラグ1つで切替

  2. T+0

    修正版がリリース

    8.5.12・8.4.27・8.3.36・8.2.35

  3. 数分

    patched イメージをビルド

    常駐ビルドと永続キャッシュ

  4. 直後

    ロールアウト

    GitOps でイメージのタグを差し替え

  5. 確認後

    メンテ解除

    稼働中の版を確認してから開放

出典: php.net の告知、GHSA-w8hw-w9fx-j79w・GHSA-6wmf-4rv5-p3rp(2026-10-21 確認)。各段の所要は一般的な緊急対応の流れで、特定企業の実測値ではない

依存パッケージから入る攻撃も含めた全体像は、「サプライチェーン攻撃とは|手口・防御ツール比較・npm 12の新機構まで実践解説」にまとめています。この緊急対応を実務で公開した例として、pixiv のエンジニアブログ(picopico「PHP 8.5.12の緊急リリースにpixivはどう対応したか」)が同種の記録を出しています。本記事はその型を、特定企業の数値や構成には依らず、一般の手順として一次情報から組み直したものです。

30秒でわかる

  • ・遅いのはコードでなく基盤:緊急の修正版は FROM の版を上げるだけ。時間を食うのはビルドと全ノードへの配布
  • ・ビルド:BuildKit を常駐させキャッシュを永続化し、COPY --linkでベース差し替え後も後続レイヤーを捨てない
  • ・配布:registry の pull-through cacheで外部レジストリへの一斉アクセスを社内ミラー1台に寄せる
  • ・拡張:緊急時に拡張を足し直す経路を、外部依存の PECL から Packagist 配布の PIE へ寄せておく
  • ・保険:公式イメージの反映を待てないときに備え、ソースからビルドする経路も1本持っておく

まずやること(次の緊急パッチまでに仕込む)

・ベースイメージの版をピンする:FROM php:8.5.12-fpm-bookworm のように版を固定し、緊急時は数字だけ差し替える
・メンテナンス画面を環境変数かフラグ1つで出せるようにする:修正版を焼いている間の露出を止める
・ビルド基盤を常駐させる:BuildKit を立ててキャッシュを永続化し、毎回ゼロから作らない
・配布のミラーを1台用意する:pull-through cache で上流への集中を避ける

PHP 緊急アップデートで時間を食うのは、コードではなくビルドと配布

修正版が出たときに本番で変えるのは、多くの場合 FROM 行の版だけです。にもかかわらず当て終えるまでに時間がかかるのは、その1行の変更がイメージ全体の作り直しを誘発し、できたイメージを数十・数百のノードへ配るのに通信が集中するからです。

事前に決めておくことは2つです。公式イメージの反映を待つか、自前でソースからビルドするか。普段は docker-library/php の新版を使い、FROM の版を上げて焼き直すのが手間も少なく安全です。ただし公式イメージの反映には公開から時差があります。より早く当てたい、あるいは公式に無い構成が要るなら、php:<版>-src タグから configure・make でソースビルドする経路を保険として持っておきます。どちらを主にするにせよ、緊急時に判断を始めないことが肝心です。

事前の二経路。公式イメージを待つ経路はdocker-library/phpの新版を使い手間が少ないが反映に時差がある。自前でソースビルドする保険はphp:<版>-srcからconfigure・makeし公式反映前でも当てられるがビルド経路の維持が要る

どちらの経路でも、速さを決めるのは次の2つの節、ビルドのキャッシュと配布のミラーです。

緊急アップデートでベースイメージを差し替えると、素朴なビルドでは後続のすべての命令が無効化され、拡張のコンパイルや Composer の依存解決までやり直しになります。ここを短くする手は2つあります。

1つ目は、BuildKit を常駐させ、キャッシュを永続化することです。CI のたびにビルダーを使い捨てるとキャッシュも消えます。BuildKit を立てたままにし、キャッシュをボリュームなどに永続化しておけば、変わっていない層は作り直しません。Buildx の Bake で複数イメージのビルド定義をまとめておくと、緊急時は1コマンドで全対象を同じキャッシュから焼けます。

2つ目が COPY –link です。moby/buildkit の Dockerfile リファレンスによると、--link を付けた COPY はソースを空の宛先ディレクトリにコピーし、それを前の状態の上に重ねるレイヤーにします。その結果、先行レイヤー(ベースイメージなど)が変わっても、このコピー層のキャッシュを再利用できます。ベースを新しいパッチ版へ差し替えても、アプリのファイルを置く層を焼き直さずに済むわけです。注意点として、--link の COPY は先行状態のファイルを読めないため、宛先パス中のシンボリックリンクを辿る前提では使えません(辿る必要がなければ公式も利用を勧めています)。

COPY --LINKビルドの高速化

ベースを新版に上げても、後続レイヤーのキャッシュを捨てない

  1. ベースを新版に

    FROM php を 8.5.12 等へ

  2. 常駐ビルド

    BuildKit を立てキャッシュを永続化

  3. COPY --link

    先行が変わっても後続は再利用

  4. patched image

    変わった分だけ焼き直す

出典: moby/buildkit Dockerfile リファレンス(COPY --link は空の宛先へ重ねるため先行レイヤーが変わっても再利用できる、2026-10-23 確認)

最小の形は、ベースを版でピンし、アプリのファイルを --link で置くだけです。

# syntax=docker/dockerfile:1
FROM php:8.5.12-fpm-bookworm AS base
COPY --link composer.json composer.lock /app/
COPY --link src/ /app/src/

緊急時は 8.5.12 の数字を新しいパッチ版に替えるだけで、COPY --link の層のキャッシュは再利用されます。

registry pull-through cache で配布の集中を避ける

ビルドが速くても、できたイメージを全ノードへ配る段で詰まると意味がありません。数十・数百のコンテナが同じイメージを外部レジストリから一斉に取りに来ると、上流への通信と出口(NAT)に負荷が集中します。ここで効くのが registry の pull-through cache(ミラー)です。

distribution/distribution のミラー設定によると、config.yml に proxy セクションを足して remoteurl を書くと、そのレジストリは外部レジストリのキャッシュとして動きます。初回の pull だけ上流から取得して保存し、二回目以降は自分のストレージから返す仕組みです。タグ指定の pull なら上流で新しい版かを確認し、古ければ取り直します。

PULL-THROUGH配布の集中を避ける

外部レジストリへの一斉アクセスを、社内のミラーでさばく

  1. 外部レジストリ

    Docker Hub など上流

  2. pull-through proxy

    初回だけ取得して保存

  3. 社内ネットワーク

    二回目以降は手元から

  4. 各ノード

    一斉取得でも上流に集中しない

出典: distribution/distribution のミラー(pull-through cache)設定。上流は一度に1つ、pull のみ(2026-10-23 確認)
# registry の config.yml
proxy:
  remoteurl: https://registry-1.docker.io
  ttl: 168h

公式ドキュメントのとおり、ミラーできる上流は一度に1つで、肩代わりするのは pull です(push の経路ではありません)。プライベートイメージを通すときだけ username・password を足し、その場合はミラー自体を認証で保護します。社内LANに1台置けば、緊急ロールアウトで全ノードが一斉に取りに来ても、上流への集中を1台に寄せられます。

PECL から PIE へ:拡張を緊急時に足せる形にする

PHP 拡張(C 拡張)を使っていると、緊急アップデートで拡張もビルドし直すことがあります。その調達経路が外部インフラ依存だと、緊急時にそこが詰まると全体が止まります。ここで関わるのが PECL から PIE への移行です。

PHP Foundation が公開した PIE(PHP Installer for Extensions)は PECL の後継にあたる拡張インストーラで、拡張を Packagist 経由の Composer メタデータで配布します。拡張の作者は composer.json を足して Packagist に登録し、PIE はプロジェクトの Composer 設定から必要な拡張を見つけ出す形です。PECL は PEAR に依存し、PHP 8 では非推奨通知やエラーが出やすく、PIE 1.0 以降は PECL が新規パッケージを受け付けない方向と報じられています。

観点 PECL(従来) PIE(PHP Foundation)
配布元 pecl.php.net(専用インフラ) Packagist(Composer と同じ土俵)
依存 PEAR に依存(PHP 8 で非推奨通知が出やすい) PHP 8.1 以降で動作。他の PHP 版の拡張も入れられる
記述 package.xml composer.json
位置づけ 後継に譲る方向 公式の後継インストーラ

拡張をビルドし直す経路を、依存の記述と同じ Composer/Packagist の土俵へ寄せておくと、緊急時に別インフラの生死を気にする調達先が1つ減ります。

自分の構成が PHP 緊急アップデートに耐えるか確認する

PHP 緊急アップデートに数分で追従できる形になっているかは、2点を機械的に確認できます。ベースイメージの版が固定されているか(可変タグだと緊急時に版を狙えない)、COPY --link でベース差し替え後にキャッシュを活かせるか、です。

git fetch --all --prune
# ベースイメージの版が固定されているか(可変タグだと緊急時に版を狙えない)
grep -REn 'FROM[[:space:]]+php:[0-9]+\.[0-9]+\.[0-9]+' .
# COPY --link を使っているか(ベース更新で後続レイヤーを捨てない)
grep -REn 'COPY[[:space:]]+--link' .

手元で確かめると、版をピンして COPY --link を使う Dockerfile では該当行が出て(終了コード0)、可変タグの FROM php:fpm と --link 無しの COPY では何も出ません(終了コード1)。これは構成が整っているかの確認であって、脆弱性の判定ではありません。この結果だけでは安全とは言えず、実際のロールアウトが数分で回るかは、ビルド基盤の常駐・配布のミラー・メンテ切替まで通して試して初めて分かります。すぐに当てられない間の時間を稼ぐ手は、WAFによる仮想パッチに別途まとめています。

参照ソース

・moby/buildkit — Dockerfile reference — COPY –link は空の宛先へ重ねるため先行レイヤーが変わっても再利用できる(2026-10-23 確認)
・distribution/distribution — Registry as a pull through cache — proxy.remoteurl のミラー設定・上流は一度に1つ・pull のみ(2026-10-23 確認)
・docker-library/php — 公式 PHP イメージ・versions.json・ソースビルド用の *-src タグ
・docker/buildx — Buildx と Bake(複数イメージのビルド定義)
・PHP Foundation — Announcing the Pre-Release of the PHP Installer for Extensions(PIE) — PIE の位置づけと PECL からの移行
・php/pie — PHP Installer for Extensions — PIE 本体
・pixiv inside — picopico「PHP 8.5.12の緊急リリースにpixivはどう対応したか」を実例として参照(内部の具体値は転載していない)