【テクニカル・上級編】Composerの「Archive」機能を活用したデプロイ:ビルド済みパッケージでCI/CDを爆速化する – ビルド・パッケージ管理ツール生産性向上バイブル

Composer ArchiveがCI/CDの常識を覆す:数万ファイルのI/O地獄から解放される「完全ビルド済みアーティファクト」戦略

開発現場でPHPのCI/CDパイプラインを構築していて、一度でも絶望したことがないエンジニアはいないはずだ。
`composer install –no-dev`。
この一見無害に見えるコマンドが、本番デプロイの直前で数分、あるいは数十分もの時間を溶かしていく。

原因は明確である。`vendor/` ディレクトリの中に蠢く数万個、場合によっては10万個以上の小さなPHPファイル群だ。これをそのままリモートサーバーへ転送したり、Dockerイメージにビルドしようとすれば、ファイルシステムのinode消費、ネットワークのTCPハンドシェイクの嵐、そして何より大量の小さなI/Oによる劇的なパフォーマンス劣化を引き起こす。

「なぜ、デプロイのたびにターゲット環境でcomposerに依存関係を解決させ、ファイルをバラバラの状態で転送しているのか?」

この問いに対するDevOps的な最終解答が、Composerの `archive` コマンドである。
今回は、開発環境(またはCIのビルドステージ)で依存関係を完全に解決し、単一のアーカイブファイル(ZIPやTAR)へと固めた上で、デプロイ先へアトミックに転送・展開する「爆速CI/CDパイプライン」の構築手法を、内部アーキテクチャの解説を交えながら徹底的に紐解いていく。

—

1. 内部アーキテクチャ:なぜ `composer archive` なのか?

多くのエンジニアは、`composer archive` を単なる「ソースコードのバックアップ用コマンド」程度に誤解している。しかし、その本質は 「Deterministic(決定論的)なパッケージングエンジン」 である。

内部で何が起きているのか?

1. メタデータの固定: `composer.lock` を読み込み、バージョンの揺らぎを完全に排除した状態で依存グラフを構築する。
2. Git/SVNインデックスの活用(デフォルト): `.gitattributes` の `export-ignore` ディレクティブを厳密に解釈し、テストコード、ドキュメント、CI設定ファイルなど、本番稼働に一切不要な肥大化要因をビルドの段階で綺麗にそぎ落とす。
3. ストリーム圧縮: 依存関係を含むすべてのファイルを単一のアーカイブにパッケージングする。

なぜこれがデプロイを爆速にするのか?

  • 転送効率の最大化: 数万個のファイルを個別転送(SFTPやSCP)すると、プロトコルのオーバーヘッドだけで膨大な時間がかかる。1つの巨大なZIPファイルであれば、単一のストリームとして限界まで帯域を使い切って転送できる。
  • ディスクI/Oの局所化: デプロイ先での展開は、OSのアーカイブ展開エンジン(`unzip` 等)がC言語レベルの最適化されたループで一気に処理するため、PHPスクリプトベースのComposerがディスクを叩くのとは比較にならないほどの速度差を生む。
  • 環境差異の完全排除: 「CI環境では動いたのに、本番のPHP拡張やComposerのバージョン違いで依存関係の解決が失敗した」という事故が物理的に起こらなくなる。ビルドした瞬間の一意なバイナリがそのまま本番の正義となるからだ。

—

2. 実践:最適化された `.gitattributes` の設計

`composer archive` を真に活かすためには、パッケージングの対象外(ignore)をコントロールする `.gitattributes` の設定が命となる。ここを適当にしていると、本番環境にテストスイートや無駄なMarkdownが混入し、セキュリティリスクやストレージ無駄遣いの原因になる。

プロジェクトルートに以下の `.gitattributes` を配置せよ。

==========================================
Composer Archive Optimization Rules
==========================================

デフォルトで全ファイルをアーカイブ対象外にする(ホワイトリスト方式の思想)

  • export-ignore

アプリケーションの根幹となるコードは除外対象から外す(アーカイブに含める)
/app export-ignore!
/bootstrap export-ignore!
/config export-ignore!
/public export-ignore!
/resources export-ignore!
/routes export-ignore!
/storage export-ignore!
/database export-ignore!

エントリーポイントと設定ファイル群
/composer.json export-ignore!
/composer.lock export-ignore!
/artisan export-ignore!

開発・テスト・CIに関するディレクトリや設定は確実に除外する
/tests export-ignore
/.git export-ignore
/.github export-ignore
/node_modules export-ignore
/storage/logs/ export-ignore
/.env export-ignore

この設定により、`composer archive` を実行した際、本番稼働に不要なファイルは最初から排除され、かつ `vendor/` ディレクトリも含めた最小限かつ完全な成果物が生成される素地が整う。

—

3. CI/CDパイプラインへの組み込み(GitHub Actionsの実装例)

それでは、この思想を実際のGitHub Actionsパイプラインに落とし込む。
ビルドステージで一度だけ `composer install` を行い、その成果物を `composer archive` で固めてアーティファクトとして保存、デプロイステージではそのZIPを転送して解凍するだけのシンプルなフローを構築する。

name: Production Deployment Pipeline

on:
push:
branches:

  • main

jobs:
build:
name: Build Artifact
runs-on: ubuntu-latest

steps:

  • name: Checkout Code

uses: actions/checkout@v4

  • name: Setup PHP Environment

uses: shivammathur/setup-php@v2
with:
php-version: ‘8.2’
tools: composer:v2.6.x
coverage: none

  • 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@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${–hash-files(‘/composer.lock’) }}
restore-keys: |
${–runner.os}-composer-

# 1. 開発環境依存関係を含めて本番用インストールを実行

  • name: Install Production Dependencies

run: composer install –no-dev –optimize-autoloader –no-interaction –prefer-dist

# 2. アーカイブの生成(vendorディレクトリを含め、ZIP形式で出力)

  • name: Generate Composer Archive

run: |
mkdir -p build-artifact
# –file で出力先を指定、–format=zip でZIP圧縮を指定
composer archive –format=zip –file=build-artifact/release-app –dir=.

# 生成されたZIPの中に正しくvendorが含まれているか構造を確認
unzip -l build-artifact/release-app.zip | grep vendor/ | head -n 10

# 3. アーティファクトを次のジョブ(デプロイ)へ受け渡すために保存

  • name: Upload Artifact

uses: actions/upload-artifact@v4
with:
name: app-release-archive
path: build-artifact/release-app.zip
retention-days: 1

deploy:
name: Deploy to Production Server
needs: build
runs-on: ubuntu-latest

steps:

  • name: Download Artifact

uses: actions/download-artifact@v4
with:
name: app-release-archive
path: ./downloaded-artifact

# 4. 本番サーバーへの転送とアトミックな展開

  • name: Deploy via SSH & Unzip

uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.PROD_HOST }}
username: ${{ secrets.PROD_USER }}
key: ${{ secrets.PROD_SSH_KEY }}
script: |
# デプロイ先の一時ディレクトリを作成
TARGET_DIR=”/var/www/html/releases/$(date +%Y%m%d%H%M%S)”
mkdir -p $TARGET_DIR

# アーカイブをサーバーへアップロード済みの前提、あるいはSCPで転送する処理
# (ここではSCPステップを省略せず、GitHub Actionsから直接転送する例)

# 注: 実際のSSHアクションではscpステップを組み合わせるか、
# appleboy/scp-actionを使って事前にZIPを転送しておくこと。

【補足】GitHub Actionsから直接ターゲットサーバーへZIPを転送して展開する完全なスクリプト片

  • name: Transfer ZIP to Production

uses: appleboy/scp-action@master
with:
host: ${{ secrets.PROD_HOST }}
username: ${{ secrets.PROD_USER }}
key: ${{ secrets.PROD_SSH_KEY }}
source: “downloaded-artifact/release-app.zip”
target: “/tmp/”

  • name: Extract and Activate on Production

uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.PROD_HOST }}
username: ${{ secrets.PROD_USER }}
key: ${{ secrets.PROD_SSH_KEY }}
script: |
RELEASE_NAME=$(date +%Y%m%d%H%M%S)
DEPLOY_PATH=”/var/www/html/releases/$RELEASE_NAME”

# ディレクトリを作成してZIPを展開
mkdir -p $DEPLOY_PATH
unzip -q /tmp/downloaded-artifact/release-app.zip -d $DEPLOY_PATH

# 共有ストレージや設定ファイル(.envなど)のシンボリックリンクを貼る
ln -nfs /var/www/html/shared/.env $DEPLOY_PATH/.env
ln -nfs /var/www/html/shared/storage $DEPLOY_PATH/storage

# 本番ウェブドキュメントルートを新しいリリースへ切り替え(アトミック・シンボリックリンク切替)
ln -nfs $DEPLOY_PATH /var/www/html/current

# 一時ファイルの削除
rm -f /tmp/downloaded-artifact/release-app.zip

# 古いリリースのクリーンアップ(直近5世代を残して削除)
cd /var/www/html/releases && ls -t | tail -n +6 | xargs -r rm -rf

このアプローチにより、本番サーバー側にはPHPやComposerの実行環境すら厳密には不要になる(※Artisanコマンド等を実行するコンテナが別にある場合)。サーバー側で行う作業は「ZIPを受け取って展開し、シンボリックリンクを付け替える」という極めて高速かつ安全な操作のみに限定される。

—

4. Dockerコンテナビルドにおける `composer archive` の極限最適化

KubernetesやECSなどのコンテナベースのインフラストラクチャにおいて、マルチステージビルドと `composer archive` を組み合わせることで、「セキュアかつ極小サイズのプロダクションイメージ」 を生成できる。

従来のDockerビルドでは、`COPY composer. ./` を行ってから `RUN composer install` を実行するため、レイヤーキャッシュのヒット率やビルドコンテキストのサイズに悩まされがちだった。

これをアーカイブベースのマルチステージビルドで完全に最適化するDockerfileを書く。

==========================================
Stage 1: Build Stage (Composer & Dependencies)
==========================================
FROM composer:2.6 AS builder

WORKDIR /build

ソースコードのコピー
COPY . /build

依存関係のインストール(開発用除く、かつ最適化)
RUN composer install –no-dev –optimize-autoloader –no-interaction –prefer-dist

アーカイブの生成(vendorを含めた完全なバイナリパッケージを作る)
※ composer archive は標準では .gitattributes の export-ignore を適用するが、
vendor ディレクトリ自体は明示的に保持させるか、git 管理下にない場合は別手法をとる必要がある。
実務上のハックとして、vendorを含めたアーカイブを作るためのシェルスクリプトを挟む。
RUN mkdir -p /output && \
zip -r /output/app.zip . -x “.git” “tests/” “node_modules/”

==========================================
Stage 2: Production Runtime Stage
==========================================
FROM php:8.2-fpm-alpine AS runtime

本番稼働に必要な最小限のシステムパッケージのみインストール
RUN apk add –no-cache \
nginx \
supervisor \
unzip \
libzip-dev

WORKDIR /var/www/html

ビルドステージから生成された完全なZIPアーカイブのみをコピー
COPY –from=builder /output/app.zip /tmp/app.zip

コンテナ内でアトミックに展開
RUN unzip -q /tmp/app.zip -d /var/www/html && \
rm /tmp/app.zip

権限の適切な調整
RUN chown -R www-data:www-data /var/www/html

EXPOSE 80

CMD [“php-fpm”]

このDocker構成の恐るべきメリット

1. ビルドコンテキストの汚染防止: ローカルマシンの `vendor/` や不要なキャッシュがDockerデーモンに送信されるのを防ぎ、ビルドのネットワーク転送量をゼロに近づける。
2. イメージの軽量化: 余分なビルドツール(Composer本体やGit、重い開発ライブラリ)が最終的なランタイムイメージに一切含まれないため、セキュリティ脆弱性(CVE)の表面積を劇的に狭めることができる。

—

5. トラブルシューティング & エキスパートハック

現場でこのアーキテクチャを導入する際、シニアエンジニアが必ず直面する「罠」と、その回避ハックを共有する。

ハック1: `composer archive` はデフォルトで `vendor/` を含まない場合がある?

正確に言えば、`composer archive` は `composer.json` と依存関係をパッケージングするが、もしプロジェクトがGit管理されていない状態や、`.git` ディレクトリが存在しない環境で実行された場合、挙動が不安定になることがある。
確実に `vendor/` を含んだアーカイブを作るためには、ビルドステージで以下のように明示的にZIPコマンドで固めるか、あるいはComposerのプラグインやカスタムスクリプトを併用するのが確実である。

Composer内部のキャッシュやAutoloader最適化を行った後にZIP化する最強のワンライナー
composer install –no-dev –optimize-autoloader –prefer-dist
zip -9 -r /tmp/production-release.zip . \
-x “.git” \
-x “tests/” \
-x “node_modules/” \
-x “var/log/” \
-x “.env” \
-x “docker”

※ `composer archive` はメタデータの検証において優れているが、純粋な「vendorを含めた全ファイルの一撃パッケージング」においては、上記のように `.gitattributes` を制覇した上での `zip` コマンドの直接叩きの方が、CIパイプラインのコントロール下では確実性が高い場合が多い。要件に合わせて使い分けよ。

ハック2: シンボリックリンク切替時(ゼロダウンタイムデプロイ)のストレージ共有

PHPアプリケーション(LaravelやSymfonyなど)では、`storage/` や `public/uploads/` などの永続データをリリースごとにリセットしてはならない。
アーカイブを展開する際、ZIPの中に古いstorageが含まれていて上書きしてしまう事故を防ぐため、展開スクリプト側で以下のようにあらかじめstorageディレクトリを排除して展開するか、展開後にシンボリックリンクを強制上書きする設計が鉄則となる。

展開先のstorageを削除し、共有ストレージへのリンクを確実に貼る
rm -rf $DEPLOY_PATH/storage
ln -nfs /var/www/html/shared/storage $DEPLOY_PATH/storage

—

結び:インフラとコードの境界線を消し去れ

「デプロイに時間がかかる」「本番環境でなぜか依存関係の解決がネットワークエラーでコケる」。
そんなプリミティブなインフラの苦悩に時間を奪われているようでは、プロダクトの価値を最大化することはできない。

`composer archive`(あるいはそれに準ずるビルド済みアーティファクト戦略)の本質は、「コードがビルドされた瞬間の一意性を、本番稼働のその瞬間まで一滴も漏らさずに運搬する」 というDevOpsの哲学そのものである。

ファイルをバラバラで運ぶ時代は終わった。
今日からあなたのパイプラインにも「完全なカプセル化」を導入し、秒速のデプロイメントを手に入れてほしい。

タイトルとURLをコピーしました