Docker Composeを使った初期装備なRails6/PostgreSQL環境の作り方 - Qiita の続編です。
前回の記事では、Quickstart: Compose and Rails | Docker Documentationに沿ってDocker Composeを使ったRuby2.7,Rails6,PostgreSQLでの開発環境を作る方法について紹介しました。
この記事では、前回の記事で構築した開発環境における問題点(不便な点)について述べたあと、Docker Composeのvolumesを使ってその問題を解決する方法について記載します。
想定する読者は前回と同じです。
- Rails環境構築でいつも躓くの方(丁寧めに説明します)
- 未来の自分(思い出せるように書きます)
問題点
結論から言うと、前回作った開発環境にはGemを追加する度にイメージをbuildしないといけないという問題点があります。
具体的にpry(IRBの代替となるGemです)を追加してみて、その流れの中で詳しく説明します。
Gemをインストールしてみる
まずGemfileにpryを追記してください。
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
+gem 'pry'
次にwebのコンテナに入ってgemをインストールしましょう。
$ docker-compose run web bash
Starting docker-compose-rails-sample_db_1 ... done
root@4c3d93084e7c:/myapp# bundle install
.......
インストールが完了するとGemfile.lockがこんな感じで書き変わると思います。
@@ -72,6 +72,7 @@ GEM
regexp_parser (~> 1.5)
xpath (~> 3.2)
childprocess (3.0.0)
+ coderay (1.1.3)
concurrent-ruby (1.1.6)
crass (1.0.6)
erubi (1.9.0)
@@ -102,6 +103,9 @@ GEM
nokogiri (1.10.9)
mini_portile2 (~> 2.4.0)
pg (1.2.3)
+ pry (0.13.1)
+ coderay (~> 1.1)
+ method_source (~> 1.0)
public_suffix (4.0.5)
puma (4.3.5)
nio4r (~> 2.0)
@@ -204,6 +208,7 @@ DEPENDENCIES
jbuilder (~> 2.7)
listen (~> 3.2)
pg (>= 0.18, < 2.0)
+ pry
puma (~> 4.1)
rails (~> 6.0.3, >= 6.0.3.1)
sass-rails (>= 6)
pryがインストールされていることを確認しましょう。先ほどのコンテナの中でpryを実行してください。
root@4c3d93084e7c:/myapp# pry
[1] pry(main)>
ちゃんとインストールされていますね!ではコンテナから出ましょう。
root@4c3d93084e7c:/myapp# exit
ここまでは特に問題なさそうにみえますね。
問題を確認する
改めてwebのコンテナに入り直してみましょう。
$ docker-compose run web bash
Starting docker-compose-rails-sample_db_1 ... done
root@c91cec1a5be0:/myapp#
さっきpryをインストールしたので、pryを起動して確認してみましょう。
root@c91cec1a5be0:/myapp# pry
bash: pry: command not found
!!!pryが入っていない!!!
なぜこの問題が起こるのか
端的にいえば「pryをインストールしたコンテナとは別のコンテナの中にいるから」です。
pryをインストールした時はroot@4c3d93084e7c:/myapp
で、先ほど確認した時はroot@c91cec1a5be0:/myapp
ですね。
4c3d93084e7c
やc91cec1a5be0
はコンテナのIDで、docker ps -a
で確認することができます。
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c91cec1a5be0 docker-compose-rails-sample_web "entrypoint.sh bash" 20 minutes ago Up 20 minutes 3000/tcp docker-compose-rails-sample_web_run_f7eaa6128b30
4c3d93084e7c docker-compose-rails-sample_web "entrypoint.sh bash" About an hour ago Exited (0) 22 minutes ago docker-compose-rails-sample_web_run_8db7cf6c1c98
この問題、dockerについて理解していれば「そりゃそうだよね〜」という感じですね。
一応ざっくり説明をしておくと、Dockerfileに基づいてイメージ(IMAGE)が作られ、イメージに基づいてコンテナ(CONTAINER)が作られます。(詳しくはDocker overview | Docker Documentation)
今回の例だと、pryがインストールされていない状態のイメージがあり、それに基づいてコンテナを作っているため、それぞれのコンテナはpryがインストールされていない状態で作られます。
それぞれのコンテナは独立しているので、コンテナ4c3d93084e7c
でpryをインストールしても、コンテナc91cec1a5be0
には関係なく、pryがインストールされていないままの状態になっています。
これを解決するには"pryがインストールされていない状態のイメージ"を"pryがインストールされている状態のイメージ"に更新する必要があります。以下のコマンドを実行するとイメージを作りなおしてくれます。(次節の説明の都合上、実行しないでください)
$ docker-compose build
作りなおしが完了したあとにコンテナに入ると、pryがインストールされた状態になります。
これで解決!、としてもいいのですが、これだとGemを追加する度に毎回buildする必要があって効率的じゃありません。
今回の場合だと(僕の環境では)イメージのサイズが1.35GBあって、これを毎回作り直すのはPCにも優しくなさそうです。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker-compose-rails-sample_web latest a889fd5e81fa 2 hours ago 1.35GB
**イメージをbuildすることなくGemの追加を反映したい!**という気持ちになります。
コンテナ間でGemのインストール内容を共有できる仕組みがあれば実現できそうです。
解決方法
Docker Composeのvolumesを使うと解決することができます。
Overview of Docker Compose | Docker Documentationの "Preserve volume data when containers are created" より、
When docker-compose up runs, if it finds any containers from previous runs, it copies the volumes from the old container to the new container. This process ensures that any data you’ve created in volumes isn’t lost.
とのこと。「docker-composeを実行するとき、過去に実行したコンテナがあればそのコンテナからvolumesを新しいコンテナにコピーするよ。このおかげでvolumesに作成したデータは失われないことが保証されるよ。」という感じですね。
書き換えてみる
ではさっそく、Compose file version 3 reference | Docker Documentationの "Volume configuration reference" を読みながら、docker-compose.yml
をこんな感じで書き換えてみることにします。
version: '3'
services:
db:
image: postgres:12.3
volumes:
- ./tmp/db:/var/lib/postgresql/data
environment:
POSTGRES_HOST_AUTH_METHOD: trust
web:
build: .
command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
volumes:
- .:/myapp
+ - bundle:/usr/local/bundle
ports:
- "3000:3000"
depends_on:
- db
+volumes:
+ bundle:
一番下のvolumes
では、bundle
という名前のvolumeを定義しています。
(bundle:
となっていて「書き忘れ?」感がありますが、これは「本当はドライバを指定できるのだけど指定しなくても良いよ。未指定の場合はdockerエンジンのデフォルトのドライバ(大抵はlocal
ドライバ)が使われるよ」という仕様に基づいています。)
そしてservices > web > volumes
では、「bundle
という名前のvolumeをコンテナ内の/usr/local/bundle
にマウントするよ」と定義しています。
この設定によって、コンテナ内で/usr/local/bundle
にインストールされたRubyGemsは、bundle
という名前のvolumeに記録され、このvolumeは新しいコンテナを作成したときにコピーされて使いまわされるようになります。
動作を確認する
では実際の動きを確認してみましょう。まずはwebコンテナでbashを起動します。
$ docker-compose run web bash
Creating volume "docker-compose-rails-sample_bundle" with default driver
Starting docker-compose-rails-sample_db_1 ... done
root@2e32d1dbc2c4:/myapp#
Creating volume "docker-compose-rails-sample_bundle" with default driver
というログが出てますね。まさにvolumeを作ったぜ、ということです。
「問題点」の節でも確認しましたが、この状態ではまだpryが入っていないはずです。
root@2e32d1dbc2c4:/myapp# pry
bash: pry: command not found
さて、Gemをインストールし、pryが入ったことを確認しましょう。
root@2e32d1dbc2c4:/myapp# bundle install
root@2e32d1dbc2c4:/myapp# pry
[1] pry(main)>
ちゃんと入りましたね。そしたらこのコンテナから出て、
root@2e32d1dbc2c4:/myapp# exit
新たにコンテナを起動しましょう。そしてpryを打ってみると、、
$ docker-compose run web bash
Starting docker-compose-rails-sample_db_1 ... done
root@f834744056f7:/myapp# pry
[1] pry(main)>
おぉ〜、入ってる。Gemがインストールされた状態を維持できています。素晴らしいですね〜
ついでにnode_modulesも
RubyGemsの話だけでいうとここまでで良いのですが、今回作っているRails6の環境ではyarnでインストールするライブラリ群についても同様の問題があります。
ついでなのでそれらについても対応してしまいましょう。
volumes:
- .:/myapp
- bundle:/usr/local/bundle
+ - node_modules:/myapp/node_modules
ports:
- "3000:3000"
depends_on:
- db
volumes:
bundle:
+ node_modules:
node_modules
というvolumeを定義して/myapp/node_modules
にマウントしているだけです。簡単ですね。
なお、今回の記事での変更点はこのプルリクエストに記録しました。
https://github.com/tanaken0515/docker-compose-rails-sample/pull/4
まとめ
今回の記事では、前回の記事で構築した開発環境におけるについて、「Gemを追加するときに毎回イメージをbuildする必要がある」という問題点を解説した上で、Docker Composeのvolumesを使ってその問題を解決する方法を記載しました。参考になれば幸いです。
個人的にもリファレンスをちゃんと読みながら作業したので勉強になりました、よかったよかった。
なお、この記事の続編として以下のような記事を書いていこうかなと思っています。
- Docker Composeで作った初期装備なRails6にVue.jsを導入する - Qiita <- 書きました(2020-06-18)
- Docker Composeでwebpack-dev-serverを動かしてもっと快適に
(予定は未定です)
ではまた〜