Laravel + Docker環境の「解決不能に見えた」500エラー:原因特定までの全記録
はじめに
Laravel, React, Docker(Vite)という構成は、モダンなWebアプリケーション開発における一般的な選択肢の一つです。しかし、その柔軟性と強力さの裏で、一度問題が発生すると原因の特定が困難になるケースも少なくありません。
本記事では、開発初期段階のログインAPI実装時に発生した、単純に見えて極めて根深い500 Internal Server Errorとの格闘の記録を共有します。当初は単純なデータベース設定の問題かと思われましたが、最終的にはDockerの環境構築そのものに潜む、複数の問題が連鎖して引き起こされていたことが判明しました。
同様の環境で開発を行うエンジニアが、不可解なエラーに直面した際のデバッグの一助となれば幸いです。
初期診断:一般的な原因と最初のアプローチ
最初に500 Internal Server Errorに遭遇した際、私たちはまず基本的な原因を疑いました。以下は、その時点での問題認識と対処法です。
主要な問題
- ログインAPI (
POST /api/v1/auth/login) で500 Internal Server Errorが発生。
初期仮説
-
データベースマイグレーションが未実行:
usersテーブルが存在しないため、認証処理で失敗している。 - PHPのDBドライバー不足: Dockerコンテナ内のPHPに、MySQLへ接続するためのドライバーがインストールされていない。
初期対応
これらの仮説に基づき、以下の手順を実行しました。
-
Dockerコンテナの正常起動を確認
docker-compose ps -
データベースマイグレーションの実行
docker-compose exec backend php artisan migrate -
テストデータのシーディング
docker-compose exec backend php artisan db:seed
多くの開発環境では、この手順で問題が解決します。しかし、今回は全てのコマンドが成功したにもかかわらず、500エラーは解消されませんでした。これが、長期にわたる詳細な調査の始まりでした。
問題の深層:複数要因の連鎖
エラーが解消されないことから、原因はさらに深い階層にあると判断し、バックエンドのログ (storage/logs/laravel.log) の詳細な分析を開始しました。その結果、エラーメッセージは状況に応じて変化し、複数の問題が複雑に絡み合っていることが明らかになりました。
1. 環境変数と設定キャッシュの罠
最初にログで確認されたのは、LaravelのMailManagerやDatabaseConnectorに起因する設定不足エラーでした。
The "url" option is required.-
MAIL_FROM_ADDRESS=nullis not supported. getaddrinfo for mysql failed: Name or service not known
これらのエラーは、都度.envファイルを修正することで対処しました。
APP_URL=http://localhost:8000MAIL_FROM_ADDRESS=hello@example.comDB_HOST=db
しかし、.envを修正しphp artisan config:clearを実行しても、修正前の設定値(例:DB_HOST=mysql)でエラーが発生し続けるという不可解な現象に遭遇しました。php artisan tinkerでconfig('database.connections.mysql.host')を実行すると正しい値(db)が返されるにもかかわらず、Webリクエスト経由では古い値が参照され続けていたのです。
これは、Laravelのファイルベースのキャッシュ(bootstrap/cache/config.php)が、config:clearコマンドで正常に削除されずに残存していたことが原因でした。最終的に、コンテナに直接入りrm bootstrap/cache/config.phpを実行することで、このキャッシュ問題を強制的に解決しました。
2. 複数のdocker-compose.ymlによる環境の歪み
上記のキャッシュ問題を解決してもなお、根本的なDB接続エラーは解消されませんでした。
最終的な原因は、プロジェクトの構造にありました。
/ (project-root)
├── docker-compose.yml # (A) サービス名: backend, db
├── api/
│ ├── docker-compose.yml # (B) サービス名: laravel.test, mysql
│ └── ... (Laravel Project)
└── ... (Frontend Project)
プロジェクト内に、内容の異なるdocker-compose.ymlが2つ存在していたのです。開発者は(A)のファイルで環境を操作しているつもりでしたが、過去の経緯から(B)のファイルが参照される瞬間があったのかもしれません。この**「信頼できる唯一の情報源(Single Source of Truth)」の欠如**が、DB_HOSTをdbにすべきかmysqlにすべきかという根本的な混乱を生み、デバッグを著しく困難にしました。
最終的な解決策と技術的教訓
全ての調査を経て、以下の手順で問題は完全に解決しました。
-
単一の
docker-compose.ymlを正とする: プロジェクトルートのdocker-compose.yml(A)を唯一の定義ファイルとし、apiディレクトリ内の(B)は削除。 -
.envの値を確定: (A)の定義(サービス名:db)に基づき、api/.envのDB_HOSTをdbに確定。 -
環境の完全再構築: 潜在的なキャッシュやボリュームの問題を根絶するため、以下のコマンドでDocker環境をゼロから再構築。
# コンテナとボリュームを完全に削除 docker-compose down -v # キャッシュを使わずにイメージを再ビルド docker-compose build --no-cache # コンテナを起動 docker-compose up -d - マイグレーションとシードの再実行: クリーンな環境で、改めてDBの初期化を実行。
この一連のデバッグから得られた教訓は以下の通りです。
-
設定ファイルは聖域である:
docker-compose.ymlのような環境定義ファイルは、プロジェクト内に一つだけ存在させるべきです。これにより、設定の不整合や意図しない挙動を防ぎます。 -
Docker環境のキャッシュは徹底的に疑う:
config:clearで解決しない問題は、より深いキャッシュ層(Dockerイメージ、ボリューム、PHP OPcache)に起因する可能性があります。環境の完全な再構築(down -v)は、問題切り分けの強力な手段です。 -
500エラーはバックエンドログが全て: ブラウザに表示される500エラーは、単なる結果通知です。原因を特定するには、必ずlaravel.logを確認し、具体的なエラーメッセージとスタックトレースを分析する必要があります。
まとめ
当初の「マイグレーション未実行」という単純な仮説から始まったこの問題は、最終的にDocker環境の構成とキャッシュ管理という、より本質的なテーマへと繋がりました。この記事が、同様の「不可解なエラー」に直面した開発者にとって、冷静な問題解決の一助となることを願っています。
Discussion