Laravelを「本番仕様」でDocker化する:Multi-stage Build、Worker、Schedulerの最適解

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

課題:なぜ一般的な方法での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化の成功を応援しています!

Share: