Docker

Laravel を使用していて Docker コンテナの起動に失敗する場合に composer install をするには docker-compose run を実行する

Docker Docker
Laravel を使用していて Docker コンテナの起動に失敗する場合に composer install をするには docker-compose run を実行する - Qiita
TL;DRコンテナを docker-compose up で起動せずに特定のコマンドを実行するには docker exec ではなく docker-compose run <container-n…

TL;DR

コンテナを docker-compose up で起動せずに特定のコマンドを実行するには docker exec ではなく docker-compose run <container-name> <command> を実行する。

発生したエラー

Docker 内で Laravel を使用していたところ、Docker コンテナの起動に失敗する現象が発生しました。

$ docker-compose up
# 中略
my_container |
my_container | Warning: require(/var/www/my-app/vendor/autoload.php): failed to open stream: No such file or directory in /var/www/my-app/artisan on line 18
my_container |
my_container | Fatal error: require(): Failed opening required '/var/www/my-app/vendor/autoload.php' (include_path='.:/usr/local/lib/php') in /var/www/my-app/artisan on line 18
my_container exited with code 255

これは composer install が実行されていないために起きるエラーですが、この記事ではこちらを解決していきます。

前提知識:Docker とボリュームのマウントについて

Docker を使用する場合、基本的にソースコードはコンテナの外に配置するようになっています。そうすることで、コンテナの中に入ることなく vim や vscode などでソースコードを編集できます。さて、これをコンテナ内と結びつけるためには docker-compose にボリュームという項目を追加し、リポジトリをマウントすることができます。

docker-compose.yml

services:
  my_container:
    # 中略
    volumes:
      - type: bind
        source: ./my-app
        target: /var/www/my-app

さて、ここで重要なのは、docker-compose 内では(つまりコンテナの起動時には)ボリュームをマウントできる一方で、Dockerfile では(つまりビルド時には)ローカルのフォルダをマウントできないということです。

https://qiita.com/embed-contents/link-card#qiita-embed-content__44f2a574be6c8bc9d80bd731dc1f363b

Dockerfileにボリュームのマウントを設定する際は, ホストマシンのディレクトリを指定することができません

となると、ビルド時に composer install を行い、その結果をローカルファイルに反映するにはひと工夫が必要になってしまいます。これを避けるために、今回は composer install はビルド時ではなくコンテナの初回起動時に行うことにします。

解決方法

まず、コンテナをビルドします。

docker-compose build my_container

次に、docker-compose up でコンテナを初回起動する前に docker-compose run コマンドを使用して composer install を行います。

docker-compose run composer install

最後に、コンテナを起動します。-d を付けるとバックグラウンドで起動します。

docker-compose up

これで無事コンテナを起動することができました。恐らく Python (pip install) や Node.js (npm install や yarn install) でも同じ方法で対処できるかなと思います。

もっと良い方法はないか?

個人開発であれば上記で問題ないこともあります。ですが、よりよい環境が作れないか考えてみましょう。

改善案1. docker-compose up 時に必ず composer install が走るようにする

Dockerfile

COPY ./my-startup-script.sh /usr/local/bin/my-startup-script.sh
RUN chmod +x /usr/local/bin/my-startup-script.sh
CMD ["my-startup-script.sh"]

my-startup-script.sh

composer install
apache2-foreground # サーバーを起動

これはお手軽で便利ですね。(スクリプトファイルを用意する代わりに bash -c でも行けると思います。)

https://qiita.com/embed-contents/link-card#qiita-embed-content__8b708d2c9b2edf62eb99570dfa123081
https://qiita.com/embed-contents/link-card#qiita-embed-content__91641ae919ebe07c3a540c55104879d0

もちろん docker-compose.yml に command を追加しても構いません。

https://qiita.com/embed-contents/link-card#qiita-embed-content__3f43e299812b6f4bb7961fbecb9ba4b4

改善案2. vendor ディレクトリをマウントしない

vendor ディレクトリ(や、node_modules ディレクトリ等)をマウントしないようにすることで、誤ってホスト側で composer install してしまうようなミスを防ぐことができます。そもそもコンテナ内でしか役に立たないファイルなので、コンテナ外から見えないようにするのは確かに合理的に思えます。このテクニックは Volume Trick と呼ばれるそうです。

以下の記事のようにボリュームの中に別のボリュームを作成することで実現できるようです。

https://qiita.com/embed-contents/link-card#qiita-embed-content__44f1466b0b3e3e62a0550afd53060b74

または、単に空のボリュームを指定するだけでよいそうです。

docker-compose.yml

services:
  my_container:
    # 中略
    volumes:
      - type: bind
        source: ./my-app
        target: /var/www/my-app
      - type: volume # ボリュームマウント
        target: /var/www/my-app/node_modules # 除外するディレクトリ
https://qiita.com/embed-contents/link-card#qiita-embed-content__e5cc8fe43beea015e14a39e199865479

さらに良いことに、これによりビルド時の vendor/ ディレクトリが引き継がれるそうです。

例えば自前で Dockerfile を用意し、Dockerfile 内部で npm install などを実行していた場合、イメージ内の node_modules を削除してしまうことになります。

これを避けるためには以下のように名前付きボリュームを用意してあげることで node_modules が名前付きボリュームに格納され、ホストマシンに同期されることがなくなります

https://qiita.com/embed-contents/link-card#qiita-embed-content__3c279ac95251758f7264fa50be4da393

ただし、vscode を使用している場合は追加設定が必要になったりなど、自力でいくつかの問題を解決する必要があるかもしれません。その際は Volume Trick などで検索すると解決策が見つかるかもしれません。

https://qiita.com/embed-contents/link-card#qiita-embed-content__20cbb26e50811b3304d174bb4c907e88

オチ

そもそも docker compose up でコンテナがエラー終了してしまうのがおかしいのでは? という話なのですが、これは Dockerfile で CMD が誤って上書きされてしまっていたからというオチでした。上書きされる前のコマンドは Docker Hub 上の元のイメージを検索すると確認できました。

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