テックリードの皆さん、日々のCI/CDパイプラインやデプロイメントの待ち時間に、イライラさせられてはいないだろうか?
「本番サーバーやステージング環境で `composer install –no-dev` を実行したら、APIのレートリミットに引っかかった」
「何万ファイルもある `vendor/` ディレクトリをそのまま `rsync` や `scp` で転送しているせいで、デプロイに数分もかかっている」
「コンテナビルドのたびに `composer` がネットワーク経由でパッケージを解決し、ビルドキャッシュが効かずに破綻している」
これらは、モダンなPHP開発現場において「アンチパターン」の極みだ。リモート環境にComposerを持ち込むこと自体が、ビルドの再現性を揺るぎないものから遠ざけ、デプロイプロセスを無駄に重くしている元凶なのだ。
今回は、Composerの隠れた(そして極めて強力な)機能である `composer archive` を用い、依存関係を含んだビルド済みパッケージ(アーティファクト)を生成・活用することで、CI/CDとデプロイを爆速化する実践アーキテクチャを伝授する。
ネットの海を漂う薄っぺらいマニュアルの翻訳ではない。実務の現場で即座にROI(投資対効果)を発揮する、プロのための実践知見をコードと共にお届けしよう。
—
なぜリモート環境での `composer install` は悪なのか?
多くのチームは、GitHub ActionsやGitLab CIなどのCI/CDパイプライン、あるいは本番サーバー上で直接 `composer install` を実行している。しかし、これには致命的なデメリットがある。
1. 外部ネットワークへの依存性: PackagistやGitHubのAPIレートリミット、あるいは一時的なネットワーク障害にデプロイが左右される。
2. 環境差異のリスク: ビルドを実行する環境(CIランナーや本番サーバー)のPHPバージョンやComposerのバージョンが微妙に異なり、ロックファイル (`composer.lock`) との不整合や予期せぬ挙動を生む。
3. I/Oのボトルネック: 数千〜数万に及ぶ `vendor/` 内の小さなファイルをリモート環境で新規作成・ダウンロードすることは、ファイルシステムへの過大な負荷と転送時間の増大を招く。
解決策:アーティファクト・パターンの採用
モダンなCI/CDの鉄則は 「ビルドは1度だけ行い、生成されたアーティファクトを各環境に使い回す」 ことだ。
開発時や信頼できるCI環境のクリーンルーム内で依存関係を解決して固め(Archive)、本番環境ではその「固まり」を解凍する(またはそのままコンテナに組み込む)だけにする。この思想をPHP(Composer)で最も美しく実現するのが `composer archive` コマンドである。
—
`composer archive` の基本と真価
`composer archive` は、プロジェクトのソースコードと、設定された依存関係(`vendor/`)を内包した単一のアーカイブファイル(ZIP, TAR, TAR.GZなど)を生成するコマンドだ。
まずは、実務で即座に使える基本コマンドとそのオプションの意図を紐解こう。
composer archive \
–format=zip \
–dir=./dist \
–file=release-v1.2.0 \
–strict \
–no-dev
- `–format=zip`: 圧縮フォーマットを指定。Windows/Linux間での互換性と展開速度を考慮し、ZIPが最も扱いやすい。
- `–dir=./dist`: 出力先ディレクトリを指定。CI/CDの成果物保存場所として直感的に扱える。
- `–file=release-v1.2.0`: アーカイブのファイル名を指定(拡張子はフォーマットに応じて自動付与)。
- `–strict`: 依存関係の解決にわずかな警告や不整合があった場合でも、ビルドを安全に失敗させる(本番品質を担保するために必須)。
- `–no-dev`: 開発用パッケージ(PHPUnitやPHPStanなど)をビルド成果物から完全に排除し、イメージサイズとセキュリティリスクを最小化する。
しかし、これをただ実行するだけでは不十分だ。本番環境に不要なファイル(`.git`, `tests/`, `.env`, CIの設定ファイルなど)までアーカイブに含まれてしまい、セキュリティ事故や容量肥大化の原因になる。
ここで鍵となるのが、`composer.json` における `archive` 設定 である。
—
チーム開発で共有すべき `composer.json` のベストプラクティス
`composer.json` の `config` や `archive` セクションを適切にチューニングすることで、生成されるアーカイブの品質を自動的に担保し、チーム全体でクリーンな成果物を共有できる。
以下に、実務でそのまま採用できるプロダクション・レディな `composer.json` の断片を示す。
{
“name”: “enterprise/core-service”,
“description”: “High-performance backend API service”,
“type”: “project”,
“require”: {
“php”: “^8.2”,
“ext-pdo”: “”,
“ext-apcu”: “”,
“guzzlehttp/guzzle”: “^7.8”
},
“require-dev”: {
“phpunit/phpunit”: “^10.5”,
“phpstan/phpstan”: “^1.10”
},
“config”: {
“optimize-autoloader”: true,
“classmap-authoritative”: true,
“preferred-install”: “dist”,
“sort-packages”: true
},
“archive”: {
“exclude”: [
“/.github”,
“/tests”,
“/var”,
“/docker”,
“/.env”,
“/phpunit.xml.dist”,
“/phpstan.neon”,
“/Makefile”,
“README.md”,
“CHANGELOG.md”
]
}
}
この設定がもたらす圧倒的なメリット
1. `classmap-authoritative: true`:
オートローダーのパフォーマンスを限界まで引き上げる。ファイルシステムへの `file_exists` チェックを一切行わず、クラス名からファイルパスへのマッピングを完全な静的マップに固定化する。これにより、本番環境でのリクエスト処理速度が向上する。
2. `archive.exclude` の厳格な定義:
テストコードやCI設定、ローカル環境変数ファイルが本番用アーカイブに混入することを物理的に防ぐ。セキュリティ(情報漏洩防止)と軽量化の両面において極めて重要である。
—
実践:GitHub ActionsによるCI/CDパイプラインの構築
理論はここまでだ。ここからは、GitHub Actions上で `composer archive` を駆使し、デプロイプロセスを劇的に高速化・堅牢化するワークフローの全体像を提示する。
以下のYAMLファイルは、ビルドジョブで作成したアーカイブをアーティファクトとしてアップロードし、デプロイジョブ側では一切Composerを使わずにそのアーカイブをデプロイする構成だ。
name: Production Deployment Pipeline
on:
push:
tags:
- ‘v’
jobs:
build:
name: Build Release Archive
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Setup PHP Environment
uses: shivammathur/setup-php@v2
with:
php-version: ‘8.2’
tools: composer:v2
coverage: none
- name: Validate composer.json and composer.lock
run: composer validate –strict
- name: Get Composer Cache Directory
id: composer-cache
run: echo “dir=$(composer config cache-files-dir)” >> $github_output
- name: Cache Composer Dependencies
uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${–hash-files(‘/composer.lock’) }}
restore-keys: |
${–runner.os }}-composer-
- name: Install Production Dependencies
run: composer install –no-dev –no-interaction –prefer-dist –no-progress
- name: Generate Production Archive
run: |
mkdir -p build
composer archive –format=zip –dir=build –file=app-release –strict –no-dev
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: app-release-archive
path: build/app-release.zip
retention-days: 7
deploy:
name: Deploy to Production Server
needs: build
runs-on: ubuntu-latest
steps:
- name: Download Release Artifact
uses: actions/download-artifact@v4
with:
name: app-release-archive
path: ./artifact
- name: Secure Transfer and Unpack on Remote Server
env:
SSH_PRIVATE_KEY: ${{ secrets.PRODUCTION_SSH_KEY }}
SSH_HOST: ${{ secrets.PRODUCTION_HOST }}
SSH_USER: ${{ secrets.PRODUCTION_USER }}
run: |
# 秘密鍵のパーミッション設定
mkdir -p ~/.ssh
echo “$SSH_PRIVATE_KEY” > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H $SSH_HOST >> ~/.ssh/known_hosts
# アーカイブファイルをリモートサーバーへ転送(数メガバイトなので一瞬で終わる)
scp ./artifact/app-release.zip $SSH_USER@$SSH_HOST:/var/www/html/releases/
# リモート側で展開し、ドキュメントルートを切り替えるアトミックデプロイの実行
ssh $SSH_USER@$SSH_HOST << 'EOF'
RELEASE_DIR="/var/www/html/releases/$(date +%Y%m%d%H%M%S)"
mkdir -p $RELEASE_DIR
# アーカイブの展開
unzip -q /var/www/html/releases/app-release.zip -d $RELEASE_DIR
# 共有ストレージ(アップロード画像やログなど)のシンボリックリンク張り替え
ln -nfs /var/www/html/shared/storage $RELEASE_DIR/storage
# 本番環境用 .env の配置
cp /var/www/html/.env $RELEASE_DIR/.env
# ウェブサーバーのドキュメントルートを新リリースへアトミックに切り替え
ln -nfs $RELEASE_DIR /var/www/html/current
# 古いリリースのクリーンアップ(直近5世代を残して削除)
ls -dt /var/www/html/releases/ | tail -n +6 | xargs rm -rf
EOF
---
プロのテックリードが教える「現場で役立つハック & トラブルシューティング」
このアーティファクト・デプロイ手法を導入するにあたり、現場で直面しがちな罠と、それを回避するためのプロの知見を共有する。
1. バイナリや拡張機能(ext-)の罠
`composer archive` は、あくまでPHPのソースコードと `vendor/` 内のスクリプトを固めるものだ。当然ながら、`ext-imagick` や `ext-redis` といったC言語ベースのPHP拡張機能そのものがZIPに含まれるわけではない。
対策: デプロイ先のサーバーやDockerベースのランタイム環境には、必要なPHP拡張機能が事前にインストールされていることを前提とする。`composer validate` や `composer check-platform-reqs` をビルドパイプラインの初期段階に組み込み、ターゲット環境とPHPバージョンの乖離を事前に検知する仕組みを必ず作っておこう。
2. Gitのバージョン情報をアプリケーションに埋め込む
アーカイブ化してしまうと、元の `.git` ディレクトリが消滅するため、アプリケーション側で `git rev-parse HEAD` などのコマンドが使えなくなり、フッター等にコミットハッシュを表示している場合に困ることがある。
対策: アーカイブを生成する直前のCIステップで、環境変数やビルド時ファイルにコミットハッシュを書き出しておこう。
ビルド時にバージョン情報ファイルを生成するスニペット
echo “ ‘${github.sha}’, ‘built_at’ => ‘”$(date -u +”%Y-%m-%dT%H:%M:%SZ”)”‘];” > config/version.php
これを `composer.json` の `archive.exclude` に含めなければ、クリーンなバージョン情報を持ったアーティファクトが完成する。
3. ローカル開発での動作確認(ドライラン)
「CI上で動いたアーカイブが、いざ本番で動かない」という恐怖をなくすため、ローカル環境でもアーカイブ生成と展開をテストするカスタムコマンドを `composer.json` の `scripts` に定義しておくことを強く推奨する。
“scripts”: {
“archive:test”: [
“Composer\\Config::disableProcessTimeout”,
“composer archive –format=zip –dir=./var/tmp –file=test-build –no-dev”,
“rm -rf ./var/tmp/test-extract && mkdir -p ./var/tmp/test-extract”,
“unzip -q ./var/tmp/test-build.zip -d ./var/tmp/test-extract”,
“@php ./var/tmp/test-extract/public/index.php”
]
}
開発端末で `composer archive:test` を実行するだけで、ビルド、除外設定の検証、展開テスト、そして簡単な動作確認までをワンストップで行える。
—
結び:デプロイの信頼性は「ビルドの分離」から始まる
Composerの `archive` 機能を軸としたデプロイメント戦略は、単なる「転送時間の短縮」に留まらない最大のメリットをもたらす。それは、「何がビルドされ、何が本番にデプロイされるのか」の完全な再現性とトレーサビリティだ。
「動いていたはずなのに、本番サーバーで `composer update` が走ってしまいバージョンが変わった」といった、エンジニアの精神衛生をすり減らすインシデントとは、今日で完全に決別しよう。
規律正しく、かつ極限まで最適化されたビルド・パイプラインを構築することこそが、チーム全体の開発スピードをネクストステージへと引き上げるテックリードの仕事だ。ぜひ、明日のデプロイフローから導入してみてほしい。