課題:なぜ一般的な方法でのLaravel Docker化では不十分なのか?
実際の運用環境でLaravelプロジェクトを6ヶ月以上稼働させて気づいたことがあります。それは、「動く」Dockerfileと「優れた」Dockerfileは全く別物だということです。
初期の頃、私はPHP-FPMとNginxを1つのコンテナに詰め込んでいました。その結果、イメージのサイズは1.2GB近くになり、デプロイのたびにイメージのプルが遅くてイライラさせられました。また、Queue WorkerやCronjobの管理も手動で行っており、およそプロフェッショナルとは言えない状態でした。
もし、あなたのコンテナが肥大化していたり、Dockerで php artisan schedule:run を実行する正しい方法が分からなかったりするなら、この記事が解決策になります。今回は、サイズを最適化し、サービスを明確に分離したスタックを構築していきます。
コアコンセプト:Multi-stage Build と単一責任の原則
Multi-stage Buildとは?
簡単に言うと、Multi-stage BuildはDockerfileを複数のステージに分ける手法です。ビルド用のステージではツール(ComposerやNode.js)をフル活用してコードをビルドし、実行用のステージ(超軽量なAlpine Linuxなど)には vendor ディレクトリなどの必要なファイルだけをコピーします。この技術により、イメージサイズを800MBから約150MBまで削減できます。
なぜコンテナを分離すべきなのか?
Dockerでは、各コンテナは一つの役割に専念すべきです(Single Responsibility)。これにより、システムのスケールが容易になり、障害の切り分けもスムーズになります。
- App Container: PHP-FPMを介してPHPのロジックを処理。
- Web Container: Nginxがリクエストを受け取り、Appコンテナへ転送。
- Worker Container:
queue:workコマンドを実行し、バックグラウンドジョブを処理。 - Scheduler Container: 定期的なタスクスケジュールを担当。
興味深いのは、App、Worker、Schedulerのすべてが**同じ1つのDockerイメージ**を共有している点です。実行コマンド(CMD)だけが異なるため、各サービス間でコードの整合性が100%保たれます。
実践:プロフェッショナルなLaravelスタックの構築
1. 最適化されたDockerfileの作成
ベースイメージには php:8.2-fpm-alpine を選択します。Alpine Linuxは、高いセキュリティと軽量さから本番環境の標準となっています。
# ステージ1:依存関係のビルド
FROM php:8.2-fpm-alpine as backend-builder
WORKDIR /var/www/html
# システム依存関係のインストール
RUN apk add --no-cache libpng-dev libzip-dev zip unzip git curl
RUN docker-php-ext-install pdo_mysql bcmath gd zip
# Composerのコピーとライブラリのインストール
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
COPY . .
RUN composer install --no-dev --optimize-autoloader --no-scripts
# ステージ2:実行用イメージ
FROM php:8.2-fpm-alpine
WORKDIR /var/www/html
# 必要な実行時依存関係のみをインストール
RUN apk add --no-cache libpng libzip
RUN docker-php-ext-install pdo_mysql bcmath gd zip
# builderステージからコードをコピー
COPY --from=backend-builder /var/www/html /var/www/html
# Laravelの標準的な権限設定
RUN chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache
EXPOSE 9000
CMD ["php-fpm"]
2. Nginxをリバースプロキシとして設定
Nginxをリバースプロキシとして設定し、トラフィックを受け取ります。fastcgi_pass app:9000 の行に注目してください。ここで指定している app は、後ほどDocker Composeファイルで定義するサービス名です。
server {
listen 80;
index index.php index.html;
root /var/www/html/public;
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass app:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
location / {
try_files $uri $uri/ /index.php?$query_string;
gzip_static on;
}
}
3. Docker Composeによる連携
このファイルでシステム全体を制御します。1つの build context を複数の目的で使い回す利便性を実感できるはずです。
version: '3.8'
services:
app:
build: .
container_name: laravel_app
restart: unless-stopped
networks:
- laravel_network
web:
image: nginx:alpine
container_name: laravel_web
volumes:
- ./:/var/www/html
- ./docker/nginx.conf:/etc/nginx/conf.d/default.conf
depends_on:
- app
ports:
- "8000:80"
networks:
- laravel_network
worker:
build: .
container_name: laravel_worker
command: php artisan queue:work --tries=3
depends_on:
- app
networks:
- laravel_network
scheduler:
build: .
container_name: laravel_scheduler
command: sh -c "while [ true ]; do php artisan schedule:run --no-interaction; sleep 60; done"
depends_on:
- app
networks:
- laravel_network
networks:
laravel_network:
driver: bridge
ヒント: WorkerやAPIからのJSONログを素早く確認したいときは、Toolcraft의 JSON Formatterが便利です。重い拡張機能を入れなくても、複雑なログ文字列を瞬時に整形してくれます。
パフォーマンスの向上:キャッシュとOpcache
本番環境では、イメージを完全に独立させるために volumes: ./:/var/www/html の指定は削除しましょう。また、**Opcache**を有効にすることも忘れないでください。opcache.ini ファイルをDockerのPHP設定ディレクトリにコピーするだけで、アプリのレスポンス速度が20〜30%向上することがあります。
まとめ
サービスを分離してLaravelをデプロイすることで、システムの運用がよりスムーズになり、メンテナンス性も向上します。将来的にジョブの実行量が増えた場合も、Webサーバーに影響を与えずに worker コンテナだけをスケールさせることができます。
もし 502 Bad Gateway が発生した場合は、docker logs laravel_app コマンドでログを確認してください。多くの場合、書き込み権限(permissions)やコンテナ間のネットワーク設定が原因です。Docker化の成功を応援しています!
