はじめに:なぜ、あなたのPHP Dockerイメージは「重く」「遅い」のか?
テックリードとして様々なプロジェクトのコードベースやCI/CDパイプラインを監査していると、PHPのコンテナ化において非常に勿体ないアンチパターンに頻繁に遭遇する。
その代表例が、単一のDockerfileで開発ツール、ビルド依存関係、そして本番用の実行ファイルをすべて混在させ、`composer install` を毎回のビルドで最初から実行しているケースだ。これではイメージサイズが1GBを超え、ビルドキャッシュは毎回破棄され、CIのフィードバックループは遅くなり、無駄なクラウドビルドコストが膨れ上がる。
Composerは非常に強力な依存関係管理ツールだが、その内部挙動(依存関係の解決、Gitリポジトリのクローン、autoloadの最適化など)とDockerのレイヤーキャッシュメカニズムを正しく理解して結合しなければ、その真価を発揮することはない。
本記事では、Dockerのマルチステージビルドを極限まで活用し、「イメージ容量の最小化」と「ビルドキャッシュの最大化」を同時に達成する、実務直結のComposer最適化手法をアーキテクトの視点から完全解説する。
—
1. Dockerマルチステージビルドの根本思想と依存関係の分離
Dockerのマルチステージビルドの本質は、「ビルドに必要な重いツールチェーン(コンパイラや開発用パッケージ)」と「実行に必要な最小限のランタイム」を完全に分離することにある。
PHP/Composerエコシステムにおいて、この原則は以下のように適用される。
1. ビルドステージ: Composer本体、Git、SSHキー(プライベートリポジトリ取得用)など、肥大化しやすいツールを許容し、`vendor` ディレクトリを生成する。
2. ランタイムステージ: PHP-FPMやWebサーバー(Nginx等)のみを含むクリーンなベースイメージに対し、ビルドステージで生成された `vendor` とソースコードだけをコピーする。
この分離により、本番イメージにComposerやGit、不要なキャッシュファイルが混入する余地を完全に断つことができる。
—
2. 実践:プロダクションレディな最適化Dockerfile
以下のDockerfileは、我々のチームが大規模プロダクション環境で標準採用している、最も洗練されたマルチステージビルドのベストプラクティス構成である。
=================================ヤンキービルドを防ぐベースステージ=================================#
セキュリティと安定性の面から固定バージョンを指定
FROM php:8.2-cli-alpine AS vendor-builder
システム依存関係の最小限のインストール(Composerの実行とZip解凍に必要)
RUN apk add –no-cache \
git \
unzip \
libzip-dev
公式からComposerバイナリを安全にマルチステージへインポート
COPY –from=composer:2.6 /usr/bin/composer /usr/bin/composer
作業ディレクトリの設定
WORKDIR /app
[重要] 依存関係レイヤーのキャッシュ最大化テクニック
ソースコード全体をコピーする前に、composerの定義ファイルだけを先に配置する
これにより、ソースコード(.php)を変更しても、依存関係が変わっていなければこのレイヤーがキャッシュされる
COPY composer.json composer.lock ./
[超重要] –no-dev, –no-scripts, –prefer-dist の戦略的使い分け
– –no-dev: 本番環境にテストツールや開発用パッケージを含めない(セキュリティと容量削減)
– –no-scripts: 依存関係インストール段階での不必要なautoload生成やスクリプト実行を防ぐ
– –prefer-dist: 可能な限りZIP配布パッケージを利用し、Git経由のcloneを避けて高速化
RUN composer install \
–no-dev \
–no-scripts \
–no-autoloader \
–prefer-dist \
–no-interaction \
–optimize-autoloader
この段階で初めてアプリケーションのソースコードをコピー
COPY . /app
ソースコードが揃った段階で、遅延させておいたスクリプトの実行とプロダクション用オートローダーの生成を行う
RUN composer dump-autoload –no-dev –classmap-authoritative –optimize
=================================本番ランタイムステージ=================================#
FROM php:8.2-fpm-alpine AS production
本番実行に必要な最低限のPHP拡張モジュールのみをインストール
RUN apk add –no-cache \
libzip-dev \
&& docker-php-ext-install zip pdo_mysql
WORKDIR /var/www/html
セキュリティ向上:不要なデフォルトファイルを削除し、非特権ユーザーで実行する基盤を作る
(※実際の運用ではここでwww-dataユーザーへの権限委譲やOPcacheの設定を行う)
[肝] vendor-builderステージから、最適化済みのvendorディレクトリだけを厳選してコピー
COPY –from=vendor-builder /app /var/www/html
権限の適切な設定
chown -R www-data:www-data /var/www/html
USER www-data
—
3. アーキテクトが解説する:Composerフラグの深層と使い分け
上記のDockerfileで使用している各フラグは、単なるおまじないではなく、Composerの内部挙動をコントロールするための精密なスイッチである。
`–no-dev`
開発環境でのみ必要なパッケージ(PHPUnit, PHPStan, Psalm, Fakerなど)をインストール対象から完全に除外する。これにより、脆弱性スキャンのヒット率を下げると同時に、イメージ容量を数十〜数百MB削減できる。
`–no-scripts` と `dump-autoload –classmap-authoritative` の分離
ここが実務で最も差がつくテクニックの一つである。
通常、`composer install` は依存関係のダウンロード後に `post-install-cmd` などのスクリプト(Laravelであればキャッシュクリアやパッケージディスカバリーなど)を実行しようとする。しかし、この時点ではまだアプリケーションのソースコードや環境変数( `.env` )が完全に揃っていないため、スクリプトがクラッシュする原因になる。
そのため、以下の2段階に分けるのが正解だ。
1. `composer install` 時は `–no-scripts` と `–no-autoloader` を指定し、純粋にライブラリのダウンロード(`vendor/` の構築)だけに特化させる。
2. ソースコードをコピーした後に、`composer dump-autoload –classmap-authoritative` を明示的に実行する。
- `–classmap-authoritative`: PSR-4のファイルシステム走査(`is_file` チェック)を完全に廃止し、クラスマップを絶対的な真実としてハードコードする。これにより、本番環境でのオートロード性能が劇的に向上する(I/Oアクセスの削減)。
`–prefer-dist`
Gitリポジトリとしてではなく、ディストリビューション(通常はzipアーカイブ)としてパッケージをダウンロードする。`.git` ディレクトリが含まれないため、容量が削減されるだけでなく、ネットワーク転送量も減り、ビルドが高速化する。
—
4. CI/CD環境におけるComposerキャッシュの永続化テクニック
Dockerのレイヤーキャッシュだけでは、CI環境(GitHub ActionsやGitLab CIなど)が毎回フレッシュな仮想マシン(ノード)で立ち上がる場合、vendorディレクトリのキャッシュをヒットさせることができない。
GitHub ActionsでComposerのキャッシュを最大限に活かすための設定例を以下に示す。
name: Production Build & Push
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
# 1. PHPとComposerのセットアップ(キャッシュキー計算に必要)
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ‘8.2’
tools: composer:v2
# 2. Composerのキャッシュディレクトリパスを特定してキャッシュアクションに渡す
- name: Get Composer Cache Directory
id: composer-cache
run: |
echo “dir=$(composer config cache-files-dir)” >> $github_output
# 3. composer.lockのハッシュをキーにしてキャッシュを復元・保存
- name: Cache Composer dependencies
uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${