Ruby on Rails 7.1 で Dockerfile を自動生成してみた！

## はじめに

Rails 7.1 [^rails] がリリースされ、`rails new` を実行した際に `Dockerfile` を自動生成してくれるようになったと聞いて、真っ先に思ったのは、

[^rails]: https://rubyonrails.org/2023/10/5/Rails-7-1-0-has-been-released

デファクトスタンダード的に、ベースとできるものがあれば「 そこからの変更には、明確な理由を必須とする 」ような方針を取れるので、

複数のリソースからのコピペによって合成されたキメラ `Dockerfile` を卒業し、より安心感のある環境が構築しやすくなりそうだ、ということでした。

また、本番環境での使用に向けて調整されているとのことなので、そういう意味でも、非常に参考になりますね。

## 雑に生成してみる

さて、何はともあれ、試してみましょう。[^pc]

[^pc]: ちなみに、本記事の執筆時に利用している PC は、MacBook Pro（ 14-inch, M2 Pro, 2023 ）で、macOS Sonoma 14.3（ Docker version 24.0.7 ）環境です。

できるだけ、ローカル（ ホスト ）環境には依存したくないし、実行環境をわかりやすくするためにも、Ruby [^ruby] の公式イメージなどを利用した Docker コンテナ上で作業するのがオススメです。

[^ruby]: https://hub.docker.com/_/ruby

とは言え、生成されたファイル群は、ホスト側にも欲しかったので、適当なディレクトリを用意してマウントしています。

```bash
docker run --rm -it -v .:/app -w /app ruby:3.2.2 bash
```

では、`rails` をインストールして、`rails new` を実行してみましょう。

```bash
gem install rails
```

↓（ バージョンを確認 ）

```bash
# rails --version
Rails 7.1.3
```

↓

```bash
rails new . --database=postgresql --css=bootstrap
```

せっかくなので、`database` と `css` も指定しておきました。

まさか、そのせいで、ちょっとしたエラーが出て、そのおかげで、自動生成についての理解が深まるなどとは、夢にも思っていませんでしたが。

そうでなければ、このテーマだけで記事を書くこともなかったかもしれません。[^next]

[^next]: 生成された `Dockerfile` の全貌が気になる方は、[次回の記事](https://qiita.com/normalsalt/items/0bea5f2efb76fa71dd61) をご参照ください。

## 試行錯誤してみる

無事に `rails new` できたでしょうか？

私はと言うと、一応、`Dockerfile` は生成されているものの、こんなエラーに遭遇してしまいました。

```
Rails::Generators::Error: The template [/usr/local/bundle/gems/cssbundling-rails-1.4.0/lib/install/bootstrap/install.rb] could not be loaded. Error: No such file or directory - npx (Rails::Generators::Error)
```

おそらく、調子に乗って Bootstrap を追加しようとしたため、要求される環境が多少なりとも厳しくなったからですね。

まぁ、もし（ Node 環境の整った ）ホスト環境で実行していたら、このエラーは気付かないうちに回避されていたかもしれないのですが、

考えてみれば「 オプションや環境が異なる場合、自動生成される `Dockerfile` にどのような違いが生まれてくるのか 」というのも、気になるところではあります。

いい機会なので、ちょっと色々と試してみることにしましょう。

### `css` オプションの確認

まず、`css` オプションを指定しなかったらどうなるのか？

```bash
rails new . --database=postgresql
```

この場合、エラーは出ることなく完了しますが、当然、`package.json` などのファイルは生成されず、`Dockerfile` にも、以下のような違いが生じるようです。

```
% diff after/Dockerfile before/Dockerfile
20c20
< # Install packages needed to build gems
---
> # Install packages needed to build gems and node modules
22c22,31
<     apt-get install --no-install-recommends -y build-essential git libpq-dev libvips pkg-config
---
>     apt-get install --no-install-recommends -y build-essential curl git libpq-dev libvips node-gyp pkg-config python-is-python3
> 
> # Install JavaScript dependencies
> ARG NODE_VERSION=18.15.0
> ARG YARN_VERSION=latest
> ENV PATH=/usr/local/node/bin:$PATH
> RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/ && \
>     /tmp/node-build-master/bin/node-build "${NODE_VERSION}" /usr/local/node && \
>     npm install -g yarn@$YARN_VERSION && \
>     rm -rf /tmp/node-build-master
28a38,41
> 
> # Install node modules
> COPY package.json yarn.lock ./
> RUN yarn install --frozen-lockfile

```

言われてみれば、当たり前の話なのですが、単に「 JavaScript の依存関係と Node モジュールのインストールなどを実行するかどうか 」が異なり、要するに `css` オプションを指定する場合には、Node 環境も利用することになります。

まぁ、開発を進めているうちに、後から Node が必要になって、`Dockerfile` に追記する場合などには、この情報（ 記述 ）も役に立つかもしれませんね。

### Node 環境の確認

次に、少し気になったのは、Node を含む場合の、そのバージョンについてです。

こちらも、実に単純な話なのですが、色々と試していると、環境によって Node のバージョン指定が異なってくることがわかります。

自動生成された `Dockerfile` で、`NODE_VERSION` という環境変数に入っている値ですね。

試しに、`rails new` する前に、次のようにして npm をインストールしておきましょう。

```bash
apt-get update -qq && apt-get install --no-install-recommends -y npm
```

ついでに、バージョンも現時点で最新の LTS に更新しておきました。

```bash
npm install -g n
n lts
hash -r
```

↓（ バージョンを確認 ）

```bash
# node --version
v20.11.0
```

この状態で、最初の時と同じように `rails new` を実行すると、どうでしょう？

前のようにエラーが出ることもなく、以下のように `Dockerfile` を比較してみるとわかりますが、どうやら、実行環境の Node のバージョンを反映してくれているようです。

```
% diff before/Dockerfile after/Dockerfile
25c25
< ARG NODE_VERSION=18.15.0
---
> ARG NODE_VERSION=20.11.0
```

というか、エラーが出た時の、デフォルト？のバージョンは `18.15.0` なんですね。

まぁ、エラーが出たならともかく、バージョンを上げたくなったら書き換えればいいだけの話なのですが、場合によっては、他に影響がないかどうか、注意が必要かもしれません。

そして、今度は `package.json` などにも、何やらちゃんと記述してくれています。

ありがとう、Rails！

```
% diff before/package.json after/package.json
3,4c3,13
<   "private": "true"
< }
---
>   "private": "true",
>   "scripts": {
>     "build:css:compile": "sass ./app/assets/stylesheets/application.bootstrap.scss:./app/assets/builds/application.css --no-source-map --load-path=node_modules",
>     "build:css:prefix": "postcss ./app/assets/builds/application.css --use=autoprefixer --output=./app/assets/builds/application.css",
>     "build:css": "yarn build:css:compile && yarn build:css:prefix",
>     "watch:css": "nodemon --watch ./app/assets/stylesheets/ --ext scss --exec \"yarn build:css\""
>   },
>   "browserslist": [
>     "defaults"
>   ]
> }
```

### Yarn 環境の確認

いや、ちょっと待って、Rails！

エラーは出なくなりましたが、`package.json` をよく見てみると、Bootstrap の利用に必要な依存関係などは、何も追加されていませんね。

確か、ちゃんと環境が整っていれば、`rails new` した際に、そこまで追記してくれていたはずです。

というか、昔は Yarn がないと、エラーが出ていたような気もしますし、先に（ Node と一緒に ）インストールしておくパターンも試してみましょう。

```bash
npm install --global yarn
```

↓（ バージョンを確認 ）

```bash
# yarn --version
1.22.21
```

さて、この状態で `rails new` を実行した場合、どうなるでしょうか？

そう言えば、`Dockerfile` にも、Node と同様、バージョンを指定するための環境変数が用意されていましたが、実行環境のバージョンを反映してくれる点も、同じようですね。[^yarn]

[^yarn]: `YARN_VERSION` のデフォルトが `latest` だというのは、まぁ、妥当なのでしょうかね？

```
# diff before/Dockerfile after/Dockerfile
26c26
< ARG YARN_VERSION=latest
---
> ARG YARN_VERSION=1.22.21
```

では、果たして、`package.json` の中身は、どうなっているのか。

```
# cat package.json 
{
  "name": "app",
  "private": "true",
  "dependencies": {
    "@popperjs/core": "^2.11.8",
    "autoprefixer": "^10.4.17",
    "bootstrap": "^5.3.2",
    "bootstrap-icons": "^1.11.3",
    "nodemon": "^3.0.3",
    "postcss": "^8.4.33",
    "postcss-cli": "^11.0.0",
    "sass": "^1.70.0"
  },
  "scripts": {
    "build:css:compile": "sass ./app/assets/stylesheets/application.bootstrap.scss:./app/assets/builds/application.css --no-source-map --load-path=node_modules",
    "build:css:prefix": "postcss ./app/assets/builds/application.css --use=autoprefixer --output=./app/assets/builds/application.css",
    "build:css": "yarn build:css:compile && yarn build:css:prefix",
    "watch:css": "nodemon --watch ./app/assets/stylesheets/ --ext scss --exec \"yarn build:css\""
  },
  "browserslist": [
    "defaults"
  ]
}
```

エクセレント！！

ちなみに、ここまでやって初めて、`node_modules` ディレクトリや、`yarn.lock` ファイルなども追加されるようなので、これらの環境が必要な場合には、注意してみてください。

## まとめ

結論としては、特に CSS フレームワークをオプションで指定する場合などには、Node & Yarn 環境を確認してから、`rails new` すればいい、ということですね。

ともあれ、Docker を使えば、気軽に様々な環境を作って自動生成を行えるので、何か気になることがあったら、実際に色々と試してみることをオススメします。

最後に、今回とてもお世話になった「 二つのディレクトリを指定して、簡単にファイル群を比較するコマンド 」を紹介して、締めとさせていただきましょう。

```
# diff -rq -x tmp -x .DS_Store before after
Files before/Dockerfile and after/Dockerfile differ
Only in after/app/assets/builds: application.css
Files before/config/credentials.yml.enc and after/config/credentials.yml.enc differ
Files before/config/master.key and after/config/master.key differ
Only in after: node_modules
Files before/package.json and after/package.json differ
Only in after: yarn.lock
```

ありがとう、diff！