Composerの深淵:`cache-files-maxsize`と`cache-read-only`で構築する、秒速ビルドと鉄壁のDocker戦略
開発環境の速度は、エンジニアの認知負荷と直接比例する。
`composer install`を実行した瞬間、数秒の沈黙が流れる。あの数秒間に、開発者はContext Switch(文脈の切り替わり)を起こし、集中力の糸が切れている。これを「たかが数秒」と切り捨てるアーキテクトは、モダンな開発フローの恩恵を半分も引き出せていない。
Composerは、単なるPHPの依存関係管理ツールではない。内部では巧妙な並行処理、Zipアーカイブのストリーミング展開、そして何層にも及ぶキャッシュ戦略が緻密に組み合わされた、高度な分散パッケージマネージャーである。
今回は、公式ドキュメントの片隅にひっそりと佇みながらも、使いこなせば開発環境のパフォーマンスとCI/CDの安定性を劇的に劇変させる2つのConfigディレクティブ――`cache-files-maxsize`と`cache-read-only`にスポットを当てる。
ネットの海を漂う「動けばいい」レベルの設定を排し、Docker、CI/CDパイプライン、そしてマルチユーザー環境の低レイヤを完全に掌握するための極限のハックを共有しよう。
—
1. 内部アーキテクチャの理解:Composerキャッシュの構造とライフサイクル
まず、Composerがどのようにキャッシュを扱っているかを理解しなければならない。これを知らずして最適化は語れない。
Composerは、デフォルトで以下のキャッシュディレクトリを持つ。
- Linux / macOS: `~/.cache/composer` (または `$COMPOSER_HOME/cache`)
- Windows: `%LOCALAPPDATA%\Composer`
このキャッシュ領域は、大きく分けて2つのレイヤで構成されている。
1. Repo Cache (`repo/`): Packagistなどのリポジトリメタデータ(JSON)を保持し、リモートサーバーへのHTTPリクエストを劇的に削減する。
2. Files Cache (`files/`): ダウンロードしたパッケージのZIPアーカイブ(`.zip`や`.tar.gz`)をそのまま保持する。
問題となるのは後者、Files Cacheである。
何も制限を設けず、マイクロサービスアーキテクチャや多数のプロジェクトを並行して開発していると、このディレクトリは数ヶ月で数十GBのモンスターと化す。古いバージョンのパッケージ、もはや二度と使われないレガシーな依存関係のアーカイブがディスク容量を圧迫し、さらにはOSのファイルシステムキャッシュ(Page Cache)のヒット率を悪化させる原因になる。
ここで登場するのが、第1の主役 `cache-files-maxsize` である。
—
2. `cache-files-maxsize` による肥大化の根絶とI/O最適化
なぜ `cache-files-maxsize` が必要なのか?
CI/CD環境、あるいはローカルのDockerコンテナ内において、ディスク容量は有限である。特にコンテナを頻繁に破棄・再構築(Ephemeral Environment)するアプローチをとる場合、無制限に膨れ上がるComposerキャッシュは、Dockerイメージのビルド時間を悪化させ、ボリュームマウントのパフォーマンス(特にmacOS上のDocker DesktopにおけるgRPC-FUSEやVirtioFSのオーバーヘッド)を直撃する。
`cache-files-maxsize` は、Composerが保持するZIPファイルのキャッシュ容量に上限(バイト単位、または `GB`, `MB` などの単位指定)を設けるための設定である。
実践:グローバル設定の最適化
この設定は、プロジェクトごとの `composer.json` に書くべきではない。開発者のマシンやCIランナーのグローバル設定(`~/.composer/config.json` または環境変数)として適用するのが正しいアーキテクチャ設計である。
以下のJSONは、グローバル設定ファイル(通常 `~/.config/composer/config.json` または `%APPDATA%/composer/config.json`)の模範解答だ。
{
“config”: {
// キャッシュファイルの最大容量を「2GB」に厳格に制限する
// これを超えた場合、LRU(Least Recently Used)アルゴリズムに基づき、
// 長期間アクセスされていない古いZIPアーカイブから自動的に削除される
“cache-files-maxsize”: “2GB”,
// キャッシュの保持期間(デフォルトは6カ月だが、アクティブな開発環境では短縮が吉)
“cache-files-ttl”: 15552000
}
}
この設定がもたらす実務的メリット
1. ディスク枯渇障害の予防: CI/CDランナー(GitHub Actions Runners等)でディスクフルエラー(`No space left on device`)を引き起こすリスクを物理的に遮断する。
2. ファイルシステム・スキャンの高速化: キャッシュディレクトリ内のファイル数が一定数に保たれるため、OSがディレクトリインデックスを走査するコスト(inodeの検索負荷)が激減する。
—
3. `cache-read-only` と共有サーバー/Dockerの完全制覇
次に、あまり知られていない隠し玉 `cache-read-only` について解説する。
なぜキャッシュの「書き込み権限」が問題になるのか?
複数人で開発する共有開発サーバー、あるいはKubernetes上のPod、さらには権限分離が厳格なDockerコンテナ環境において、次のようなエラーに直面したことはないだろうか?
[RuntimeException]
PHP Notice: chmod(): Operation not permitted in …
原因は明確だ。Composerはデフォルトで、ダウンロードしたパッケージのキャッシュを自身のグローバルキャッシュディレクトリに書き込もうとする。しかし、実行ユーザー(例: `www-data` やコンテナ内の非特権ユーザー `appuser`)にそのディレクトリへの書き込み権限がない場合、あるいはファイルシステムが読み取り専用(Read-only Root Filesystem)としてマウントされている場合、Composerは盛大にクラッシュする。
ここで `cache-read-only` を `true` に設定する。
実践:読み取り専用キャッシュ戦略
この設定を有効にすると、Composerは「ローカルのキャッシュディレクトリから読み込みは行うが、新規のキャッシュ書き込み(ダウンロードしたアーカイブの保存)を一切行わなくなる」。
Dockerのマルチステージビルドや、CI環境において非常に強力な武器となる。
{
“config”: {
// キャッシュへの書き込みを完全に無効化し、読み取り専用として扱う
// 権限エラーを完全に回避しつつ、既存のキャッシュヒットによる高速化の恩恵だけを享受する
“cache-read-only”: true
}
}
Dockerコンテナにおける究極の活用パターン
プロダクション向け、あるいは厳格なセキュアCIパイプライン向けのDockerfileでは、ルートファイルシステムを読み取り専用(Read-only)にしつつ、Composerの高速な依存関係解決を両立させたいという要求が生じる。
以下のDockerfileおよびCompose構成は、その要求を完璧に満たすアーキテクチャの極みである。
— ステージ 1: ビルダー(書き込み可能環境で依存関係を解決) —
FROM php:8.3-cli AS builder
WORKDIR /app
システム依存関係とComposerのインストール
RUN apt-get update && apt-get install -y git unzip \
&& curl -sS https://getcomposer.org | php — –install-dir=/usr/local/bin –filename=composer
ホスト側で用意された事前ビルド済み/共有キャッシュをコンテナにコピー(あるいはボリュームマウント)
このステージでは書き込み権限を完全に与えておく
COPY composer.json composer.lock ./
RUN composer install –no-dev –optimize-autoloader –no-interaction
— ステージ 2: ランタイム(読み取り専用・セキュアな本番イメージ) —
FROM php:8.3-cli AS runtime
WORKDIR /app
セキュリティ強化のため、非特権ユーザーで実行
USER www-data
ビルダーからvendorディレクトリのみをコピー
COPY –chown=www-data:www-data –from=builder /app/vendor /app/vendor
COPY –chown=www-data:www-data . /app
【ここがキモ】
ランタイム環境では書き込み権限のない領域や、厳格なセキュリティポリシーが敷かれるため、
コマンドライン引数、または環境変数で cache-read-only を有効化する
ENV COMPOSER_CACHE_READ_ONLY=1
以後、コンテナ内で万が一 composer コマンドが実行されても、
権限エラーや予期せぬディスク書き込みが発生しない
環境変数で制御する場合、`COMPOSER_CACHE_READ_ONLY=1` を指定するだけで、`config.json` を書き換えることなく同様の効果を得られる。これがCLIツールとしてのComposerの極めて優れた設計思想である。
—
4. 高度なCI/CDパイプライン統合:GitHub Actionsでの極限最適化
理論はここまでだ。これを実際のCI/CDパイプライン(GitHub Actions)に組み込み、毎回のビルドを「秒速」へと昇華させる完全なワークフロー設定を提示する。
以下の設定では、先ほど解説したキャッシュの最大容量制御と、キャッシュのリストア/セーブのライフサイクルを美しく統合している。
name: “Production Build & Test”
on:
push:
branches: [ “main” ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ‘8.3’
tools: composer:v2
- name: Get Composer Cache Directory
id: composer-cache
run: |
echo “dir=$(composer config cache-files-dir)” >> $GITHUB_OUTPUT
# GitHub Actionsのキャッシュ機構とComposerのFiles Cacheを結合する
- name: Cache Composer Dependencies
uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
# composer.lock のハッシュをキーにしてキャッシュを特定
key: ${{ runner.os }}-composer-${–hash files(‘composer.lock’) }}
restore-keys: |
${{ runner.os }}-composer-
# 【アーキテククトの知見】
# CI環境において、キャッシュが数GBに肥大化してGitHub側のキャッシュ保存制限(通常10GB)に
# 引っかかる、あるいはネットワーク転送速度がボトルネックになるのを防ぐため、
# グローバル設定で明示的に上限(cache-files-maxsize)を強制する。
- name: Configure Composer Cache Limits
run: |
composer config –global cache-files-maxsize “500MB”
- name: Install Dependencies (Using Optimized Cache)
run: |
composer install –no-progress –no-interaction –prefer-dist –optimize-autoloader
- name: Run Test Suite
run: |
vendor/bin/phpunit
このパイプラインが叩き出す卓越したパフォーマンス
1. キャッシュの肥大化防止: 毎回どれだけ大きなパッケージを扱おうとも、`cache-files-maxsize “500MB”` の戒めにより、GitHub Actionsへアップロードするキャッシュアーカイブのサイズが物理的に500MB以下に制限される。これにより、キャッシュのアップロード・ダウンロードにかかるネットワークI/Oの無駄が完全に消滅する。
2. 完全な再現性: `composer.lock` に基づく正確なバージョンの復元と、高速なディストリビューション(`–prefer-dist`)の組み合わせにより、依存関係の解決フェーズが数秒で完了する。
—
5. エキスパートのためのトラブルシューティングと内部挙動の覗き見
最後に、低レイヤの動作確認方法を伝授する。設定が正しく効いているか、感覚ではなくデータで証明できなければDevOpsエンジニアとは言えない。
Composerの内部で何が起きているかを確認するには、`-vvv`(verbose)オプションをつけて実行する。
composer install -vvv –dry-run
このコマンドを実行すると、以下のようなログが出力される。
Reading C:/Users/name/AppData/Roaming/composer/config.json
Cached equated to read-only mode: true (env var COMPOSER_CACHE_READ_ONLY)
GC: scanning cache directory C:/Users/name/AppData/Local/Composer/files
GC: pruning cache…
もし `cache-files-maxsize` やガベージコレクション(GC)がどのように動作しているか直接確認したい場合は、Composerのキャッシュディレクトリを手動で覗いてみるとよい。容量超過時に、LRUに基づきファイルが綺麗にパージされている様子が確認できるはずだ。
—
結びにかえて:ツールを「使われる側」から「支配する側」へ
多くの開発者は、フレームワークやライブラリ、そしてパッケージマネージャーの「デフォルト設定」という名の既定路線の上を走っているだけだ。しかし、システムがスケールし、チームの規模が拡大し、CI/CDのビルド時間がビジネスの足かせになり始めた瞬間、そのデフォルトは牙を剥く。
今回解説した `cache-files-maxsize` と `cache-read-only` は、単なる設定項目の名前ではない。それは、「限られたリソース(ディスク、I/O、ネットワーク)を極限までコントロールし、開発体験のラグを徹底的に排除する」という、エンジニアリングの美学そのものである。
あなたのローカル環境、そしてCI/CDパイプラインのコンフィグを今すぐ開き、この知見を実装せよ。ビルドの完了を待つ数秒の静寂が消え去ったとき、本当の意味での「開発の加速」が始まる。