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 にボリュームという項目を追加し、リポジトリをマウントすることができます。
services:
my_container:
# 中略
volumes:
- type: bind
source: ./my-app
target: /var/www/my-app
さて、ここで重要なのは、docker-compose 内では(つまりコンテナの起動時には)ボリュームをマウントできる一方で、Dockerfile では(つまりビルド時には)ローカルのフォルダをマウントできないということです。
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 が走るようにする
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"]
composer install
apache2-foreground # サーバーを起動
これはお手軽で便利ですね。(スクリプトファイルを用意する代わりに bash -c
でも行けると思います。)
もちろん docker-compose.yml
に command
を追加しても構いません。
改善案2. vendor ディレクトリをマウントしない
vendor ディレクトリ(や、node_modules ディレクトリ等)をマウントしないようにすることで、誤ってホスト側で composer install
してしまうようなミスを防ぐことができます。そもそもコンテナ内でしか役に立たないファイルなので、コンテナ外から見えないようにするのは確かに合理的に思えます。このテクニックは Volume Trick と呼ばれるそうです。
以下の記事のようにボリュームの中に別のボリュームを作成することで実現できるようです。
または、単に空のボリュームを指定するだけでよいそうです。
services:
my_container:
# 中略
volumes:
- type: bind
source: ./my-app
target: /var/www/my-app
- type: volume # ボリュームマウント
target: /var/www/my-app/node_modules # 除外するディレクトリ
さらに良いことに、これによりビルド時の vendor/ ディレクトリが引き継がれるそうです。
例えば自前で Dockerfile を用意し、Dockerfile 内部で npm install などを実行していた場合、イメージ内の node_modules を削除してしまうことになります。
これを避けるためには以下のように名前付きボリュームを用意してあげることで node_modules が名前付きボリュームに格納され、ホストマシンに同期されることがなくなります
ただし、vscode を使用している場合は追加設定が必要になったりなど、自力でいくつかの問題を解決する必要があるかもしれません。その際は Volume Trick などで検索すると解決策が見つかるかもしれません。
オチ
そもそも docker compose up
でコンテナがエラー終了してしまうのがおかしいのでは? という話なのですが、これは Dockerfile で CMD
が誤って上書きされてしまっていたからというオチでした。上書きされる前のコマンドは Docker Hub 上の元のイメージを検索すると確認できました。