【Docker】node_module の volume 設定に関して(Rails / ruby,node環境)
RailsのDocker開発環境を構築している際に、node_module の volume 設定が上手くいかず、改めて Docker について調べた内容を備忘録として残しておく。
結論:Dockerfile, docker-compose.ymlの設定
まずは、結論から示す。Railsの開発環境として、最終的に以下の設定を行なった。
# ディレクトリ構成 . ├── rails-app/ ├── Dockerfile └── docker-compose.yml
FROM ruby:3.1.2-alpine3.16 RUN apk update && apk upgrade \ && apk add --no-cache build-base \ libxml2-dev libxslt-dev \ mysql-client mysql-dev \ git bash less curl RUN curl -fsSL https://deb.nodesource.com/setup_16.16 | bash - && \ apk --no-cache add nodejs npm && \ npm install --global yarn RUN apk --no-cache add tzdata && \ cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \ apk del tzdata ENV APP_ROOT /app RUN mkdir $APP_ROOT WORKDIR $APP_ROOT COPY ./rails-app/Gemfile $APP_ROOT/ COPY ./rails-app/Gemfile.lock $APP_ROOT/ RUN bundle config set --local path vendor/bundle RUN bundle config set force_ruby_platform true && \ bundle install -j4 COPY ./rails-app/package.json $APP_ROOT/ COPY ./rails-app/yarn.lock $APP_ROOT/ RUN yarn install
version: "3.9" services: app: build: context: . dockerfile: ./Dockerfile image: ruby-node volumes: - node_modules:/app/node_modules - gem_data:/usr/local/bundle - ./rails-app:/app:cached ports: - 3000:3000 depends_on: - db stdin_open: true tty: true db: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: password TZ: Asia/Tokyo ports: - 3306:3306 command: --default-authentication-plugin=mysql_native_password volumes: - mysql_data:/var/lib/mysql volumes: node_modules: driver_opts: type: none device: ${PWD}/rails-app/node_modules o: bind gem_data: mysql_data:
コンテナ内のnode_modulesが消える
最初に発生した事象は「コンテナ内のnode_modulesが消える」である。
まず、Dockerfileをビルドして、imageを作成する。--no-cache
をつけておくことで、キャッシュを使用せず、初めからビルドを実行することができる。
docker compose build --no-cache
Dockerfileで作成したimage 「ruby-node」からコンテナを起動して、node_moduleがインストールされているかを確認する。
# docker run -it ruby-node:latest bash bash-5.1# ls Gemfile Gemfile.lock node_modules package.json yarn.lock bash-5.1# cd node_modules/ bash-5.1# ls @ampproject buffer-from datatables.net-rowgroup-bs4 ev-emitter is-arguments object-inspect shallow-copy @babel call-bind datatables.net-rowreorder eve-raphael is-core-module object-is source-map ~途中省略~ buffer-equal datatables.net-rowgroup esutils ion-rangeslider node-releases setimmediate bash-5.1# exit
node_moduleはインストールされており、imageに問題がないことを確認することができた。
ゆえに、docker-compose.yml
の設定に問題があることになる。この当時の設定ファイルは以下であった。
services: app: build: context: . dockerfile: ./Dockerfile image: ruby-node volumes: - gem_data:/usr/local/bundle - ./rails-app:/app:cached (以下、省略)
原因は、バインドマウントであった。./rails-app:/app:cached
の部分で、ホスト側の/rails-app
をコンテナ側の/app
にマウントしている。このバインドマウントでは、常にホスト側が優先されるため、ホスト側にnode_modulesが存在しない場合、コンテナ内のnode_modulesが削除されてしまう。バインドマウントの詳細は以下の公式ドキュメントを参照してほしい。
対策として、node_modulesをボリュームに保存するように、volumes
に- node_modules:/app/node_modules
を追加した。
services: app: build: context: . dockerfile: ./Dockerfile image: ruby-node volumes: - node_modules:/app/node_modules - gem_data:/usr/local/bundle - ./rails-app:/app:cached (途中、省略) volumes: node_modules: gem_data: mysql_data:
ボリュームとは、簡単に言うとDocker上に保存されるストレージである。
Docker上の/var/lib/docker/volumes/<VOLUME-NAME>/_data
に、ボリュームに設定したデータが保存される。そのため、ホストからデータを参照することができない。以下で確認することができる。
# ボリューム一覧 docker volume ls # ボリュームの詳細確認 docker volume inspect <VOLUME NAME> # ボリューム削除 docker volume rm <VOLUME NAME>
詳細は公式ドキュメントを参照してほしい。
バインドマウントとボリュームを同時に設定した場合、ボリュームが優先される。
そのため、node_modulesに関しては、ホスト側の影響を受けることがなくなり、node_modulesが削除されません。 ただし、この方法では、node_modulesはバインドマウントから除外されることになり、ホスト側のnode_modulesが空になってしまう。
ホスト側のnode_modulesが空になる
上記に記載したが、バインドマウントとボリュームの優先順位の問題で、コンテナ側のnode_modulesは削除されることがなくなるものの、node_modulesがバインドマウントの対象から除外されることにより、ホスト側のnode_modulesが空になってしまうのである。
対応方法として、ボリュームに保存しているnode_modulesをホスト側にバインドする設定をdriver_opts
で行う。
(上記、省略) volumes: node_modules: driver_opts: type: none device: ${PWD}/rails-app/node_modules o: bind gem_data: mysql_data:
driver_opts
では、ボリュームが使うドライバに対して、オプションをキーバリューのペアで指定することができる。ドライバのデフォルトは、Docker Engineで使用するように設定されているドライバであり、多くの場合はlocal
である。local
ドライバーは、Linuxのmount
コマンドと同様のオプションを受け付ける。(o: bind
と設定している根拠はこれのようだ。詳細はこちらから確認してみて欲しい。)
このようにな設定で、Dockerの名前付きボリュームをホスト側の任意のファイルパスに作成することができるようだ。
コンテナ起動時に、ホスト側にnode_modulesをマウントするので、少し時間がかかったが、無事にホスト側でnode_modulesの中身を確認することができた。
ただし、ホスト側にnode_modulesのフォルダがない場合、エラーになるのでコンテナ起動前にフォルダを作成しておく必要がある。
(.keep
を入れてフォルダのみを作成しておこうとしたが、その場合にはホスト側のnode_modulesでコンテナ側を上書きしてしまうことになったので、ダメだった。node_modulesのフォルダ作成はマニュアルで対応することにした。)
まとめ
Docker について調べて改めて理解が深まった気がした。
無事にnode_modulesの最適な構築方法を模索できてよかった。
gem_dataに関しても、通常bundleの設定を変更しない場合、/usr/local/bundle
にgemがダウンロードされる。しかしながら、/usr
配下はDockerのLinuxマシンを参照するので、他のプロジェクトと競合する可能性もあるので、RUN bundle config set --local path vendor/bundle
でコンテナ内部のパスにしておくほうがいいと思えた。