VSCode と devcontainer で作る Rails 開発環境

この記事は [WESEEK Advent Calendar 2020](https://qiita.com/advent-calendar/2020/weseek) 20日目の記事です。
# はじめに
みなさんフルスタックで全部盛りな Ruby on Rails の開発環境はどうやって作っていますでしょうか？
ローカルに ruby や DB を直にインストール？
VirtualBox などを使って VM を立ててその上に必要なものをインストール？
はたまた vagrant の box のような技術を使い、諸々インストール済みの VM を使う？
やり方は色々ありますね。
この記事では VSCode と devcontainer を使って Rails の開発環境の作り方を紹介したいと思います。
VScode と devcontainer そのもにについては [こちらの記事](https://qiita.com/haruhikonyan/items/291e1e5413a827fc6d9a) を参照していただければと思います。
こちらで紹介したエッセンスをふんだんに使っています。

# 御託はいいからとにかく立ち上げたい人へ
https://github.com/haruhikonyan/rails-devcontainer/tree/rails6.1.0-and-mysql
こちらをクローンして VSCode から devcontainer を立ち上げて devcontainer 内で `# rails s -b 0.0.0.0` と打てば `rails new` しただけの Rails6.1 が立ち上がります。

```bash:ホスト側手順
$ git clone https://github.com/haruhikonyan/rails-devcontainer -b rails6.1.0-and-mysql rails6.1.0-and-mysql
$ cd rails6.1.0-and-mysql
$ code .
```

VSCode が立ち上がる、もしくは手動で VSCode から開く

[こちら](https://qiita.com/haruhikonyan/items/291e1e5413a827fc6d9a#%E5%88%A9%E7%94%A8%E6%96%B9%E6%B3%95) を参考に VSCode から devcontainer を立ち上げられる環境を構築して開く。

devcontainer が無事立ち上がったら以下実行

```bash:コンテナ内手順
# rails s -b 0.0.0.0
```

ブラウザから `localhost:3000` にアクセスすると。。。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100251/32f51977-5600-2c2b-34a8-fb69fa20cf53.png)

clone して開くだけで開発がスタートできます！！

[コチラ](https://qiita.com/haruhikonyan/items/291e1e5413a827fc6d9a#vscode) で紹介している Remote Explorer を使って CONTAINERS の Other Containers にある `rails_devcontainer (rails_devcontainer_webpack_1)` を選択して `Show Container Log` を出しておけば webpacker のログも見ることができ、rails でのフロントエンド開発に役立つと思います。

おまけではありますが、mysql を利用しているので、中身のデータが手軽に WebUI から見られる [phpmyadmin](https://www.phpmyadmin.net/) が `localhost:8000` でアクセスできるようになっています。
> id: `root`
> password: `password`

### このリポジトリを使う注意点
- master.key コミットしてるので注意
    - サンプルとしてすぐ動かせるようにコミットしています
    - あえて [.gitignore](https://github.com/haruhikonyan/rails-devcontainer/blob/62c63c752d9a0b6af74a00e0abeb73f8693cb19c/.gitignore#L33) のコメントアウトを外している状態
    - production で使う場合は [credentials.yml.enc](https://github.com/haruhikonyan/rails-devcontainer/blob/62c63c752d9a0b6af74a00e0abeb73f8693cb19c/config/credentials.yml.enc) と合わせて再作成してコミットには含めないようにしましょう
- ポート競合  

```
ERROR: for rails  Cannot start service rails: driver failed programming external connectivity on endpoint rails610-and-mysql_devcontainer_rails_1 (3ae06361400910dfa02e66e6f05319a1323da5fe5be0086f62783aa7cd51db81): Bind for 0.0.0.0:3000 failed: port is already allocated
```
こんなエラーが出てきた場合
書いてあるとおりですが、すでに 3000 番ポートを別の何かが使用していないでしょうか？
3000 番ポートは非常に被りやすく、いろいろなものを立ち上げてるとすぐに競合してしまいます。とはいえデフォルトがそうなだけですぐ変えることができるので変更してみましょう。
[ココ](https://github.com/haruhikonyan/rails-devcontainer/blob/62c63c752d9a0b6af74a00e0abeb73f8693cb19c/.devcontainer/docker-compose.yml#L10) の値を下記のように変えるだけです。

```yml:docker-compose.yml
services:
  rails: &app
    build:
      context: .
      dockerfile: Dockerfile
    working_dir: /app
    ports:
      - 8080:3000 # 前半を任意の値に変更
```
この例だっとホスト側の 8080 番ポートをコンテナの 3000 番ポートにフォワードするっという設定になります。
同じようにおまけの [phpmyadmin は 8000 番ポートを利用している](https://github.com/haruhikonyan/rails-devcontainer/blob/62c63c752d9a0b6af74a00e0abeb73f8693cb19c/.devcontainer/docker-compose.yml#L43) のでもし被ってしまうようであれば同様に修正しましょう。

# 解説
## [Dockerfile](https://github.com/haruhikonyan/rails-devcontainer/blob/rails6.1.0-and-mysql/.devcontainer/Dockerfile)
- ベースイメージ
[devcontainer の解説記事](https://qiita.com/haruhikonyan/items/291e1e5413a827fc6d9a) では [microsoft が用意してくれた専用の image を使うと良い](https://qiita.com/haruhikonyan/items/291e1e5413a827fc6d9a#dockerfile) と書き、[ruby の image](https://github.com/microsoft/vscode-dev-containers/tree/master/containers/ruby) も用意はされているのですが ruby のバージョンが細かく指定できなかったため今回は採用を見送ってます。  
2.7系とざっくりとは指定ができるのでバージョンを細かく指定する必要が無いとかであればそちらを採用してもよいかもしれません。その場合 [node はすでに image に入っている](https://github.com/microsoft/vscode-dev-containers/blob/e562785200b9586d1eb9219fdcd34dfa9b730017/containers/ruby/.devcontainer/base.Dockerfile#L23-L31) ため下記の node に関連するもののインストールは不要だと思います。
- node インストール  
rails6 系ではそのまま `rails new` すると標準で webpacker が付いてくるため、実行環境に node が必要です。  
そのために [FROM で node image を指定し](https://github.com/haruhikonyan/rails-devcontainer/blob/62c63c752d9a0b6af74a00e0abeb73f8693cb19c/.devcontainer/Dockerfile#L1)、そこから [node の実行ファイルを COPY](https://github.com/haruhikonyan/rails-devcontainer/blob/62c63c752d9a0b6af74a00e0abeb73f8693cb19c/.devcontainer/Dockerfile#L6) してきています。
- npm インストール  
npm も必要なのでこちらは [そのままインストール](https://github.com/haruhikonyan/rails-devcontainer/blob/rails6.1.0-and-mysql/.devcontainer/Dockerfile#L8)しています。
- bundler と yarn のバージョン指定  
ruby イメージと node イメージには標準で bunder と yarn がインストールされていますが、バージョンによって挙動が違ったりするので、[明示的にバージョンを指定してインストール](https://github.com/haruhikonyan/rails-devcontainer/blob/62c63c752d9a0b6af74a00e0abeb73f8693cb19c/.devcontainer/Dockerfile#L10-L15) するようにしています。

## [docker-compose.yml](https://github.com/haruhikonyan/rails-devcontainer/blob/rails6.1.0-and-mysql/.devcontainer/docker-compose.yml)
### rails
- [environment](https://github.com/haruhikonyan/rails-devcontainer/blob/62c63c752d9a0b6af74a00e0abeb73f8693cb19c/.devcontainer/docker-compose.yml#L11-L17)
    - 日本対応  
    [environment](https://github.com/haruhikonyan/rails-devcontainer/blob/62c63c752d9a0b6af74a00e0abeb73f8693cb19c/.devcontainer/docker-compose.yml#L11-L13) でタイムゾーンと言語を設定してます。
    特に言語の設定をしないと `rails c` で日本語が打てなかったり何かと不便だったりします。
    - [DB に関する環境変数](https://github.com/haruhikonyan/rails-devcontainer/blob/62c63c752d9a0b6af74a00e0abeb73f8693cb19c/.devcontainer/docker-compose.yml#L14-L16)  
    [database.yml](https://github.com/haruhikonyan/rails-devcontainer/blob/rails6.1.0-and-mysql//app/config/database.yml) と合わせて確認してほしいのですが、全環境を環境変数で設定できるようにして、environment を意識するだけで良いようにしています。もちろん好みはあると思うので本番は別 key にしたり [config gem](https://github.com/rubyconfig/config) を使うようにしたりしても良いかもしれません。
    - [WEBPACKER_DEV_SERVER_HOST](https://github.com/haruhikonyan/rails-devcontainer/blob/62c63c752d9a0b6af74a00e0abeb73f8693cb19c/.devcontainer/docker-compose.yml#L17)  
こちら webpack-dev-server を rails の実行しているホスト以外で実行する場合の連携を制御できる環境変数であり、後述する webpack を別のコンテナで起動し、[1コンテナ1プロセス](https://docs.docker.jp/engine/articles/dockerfile_best-practice.html#id4) を実現しています。
- [env_file](https://github.com/haruhikonyan/rails-devcontainer/blob/rails6.1.0-and-mysql/.devcontainer/docker-compose.yml#L18-L19)  
.env ファイルを指定しており、独自の環境変数が設定できます。[.gitignore](https://github.com/haruhikonyan/rails-devcontainer/blob/62c63c752d9a0b6af74a00e0abeb73f8693cb19c/.gitignore#L42) で除外設定しているのでコミットはされないので、外部サービスのアクセストークンなどはこちらに設定するのが良いと思います。この設定を行うとファイルが存在しないと起動時にエラーが出るので注意です。後述する initializeCommand にて自動生成されるのでこのリポジトリを使う分にはエラーは気にする必要はないはずです。
- [volumes](https://github.com/haruhikonyan/rails-devcontainer/blob/rails6.1.0-and-mysql/.devcontainer/docker-compose.yml#L20-L24)  
基本的には [コチラ](https://qiita.com/haruhikonyan/items/291e1e5413a827fc6d9a#docker-composeyml) で紹介してる用法を使っています。
    - [リポジトリのディレクトリ](https://github.com/haruhikonyan/rails-devcontainer/blob/62c63c752d9a0b6af74a00e0abeb73f8693cb19c/.devcontainer/docker-compose.yml#L21)  
    大元
    - [Rails が作成する動的ファイルのディレクトリ](https://github.com/haruhikonyan/rails-devcontainer/blob/62c63c752d9a0b6af74a00e0abeb73f8693cb19c/.devcontainer/docker-compose.yml#L22)  
    コミットに含めなくても良いかつ、保持する必要もないディレクトリを volume にしています。もしかしたら logs 以外にもあるかも？
    - [依存ライブラリが格納されるディレクトリ](https://github.com/haruhikonyan/rails-devcontainer/blob/62c63c752d9a0b6af74a00e0abeb73f8693cb19c/.devcontainer/docker-compose.yml#L23-L24)  
    `bundle install` でダウンロードされる ruby のライブラリと `yarn install` でダウンロードされる node のライブラリを docker の named volume で保持します。

### webpack
- webpack-dev-server 専用コンテナ  
[yaml アンカー](https://github.com/haruhikonyan/rails-devcontainer/blob/62c63c752d9a0b6af74a00e0abeb73f8693cb19c/.devcontainer/docker-compose.yml#L4) で rails の設定をまるまる持っておき、[流用](https://github.com/haruhikonyan/rails-devcontainer/blob/62c63c752d9a0b6af74a00e0abeb73f8693cb19c/.devcontainer/docker-compose.yml#L26-L27) しています。  
- weboack-dev-server 起動コマンド  
devcontainer として bash を使う rails コンテナとは違い [command](https://github.com/haruhikonyan/rails-devcontainer/blob/62c63c752d9a0b6af74a00e0abeb73f8693cb19c/.devcontainer/docker-compose.yml#L28) を定義し、自動的に webpack-dev-server が起動するようにしています。
- auto reload 用ポート設定  
webpack-dev-server では js ファイルを監視してくれてファイルが save されると自動的にブラウザを更新してくれます。そのための [ポートの設定](https://github.com/haruhikonyan/rails-devcontainer/blob/62c63c752d9a0b6af74a00e0abeb73f8693cb19c/.devcontainer/docker-compose.yml#L29-L30) をしています。

### db
- 認証設定変更  
mysql8 系から認証の設定が変わり、そのままだと rails からのアクセスができないので [元に戻すコマンド](https://github.com/haruhikonyan/rails-devcontainer/blob/62c63c752d9a0b6af74a00e0abeb73f8693cb19c/.devcontainer/docker-compose.yml#L33) を設定しています。
- [phpmyadmin](https://hub.docker.com/r/phpmyadmin/phpmyadmin/)  
phpmyadmin の公式コンテナの [デフォルトの設定で接続先の DB ホスト名は db](https://github.com/phpmyadmin/docker#usage-with-linked-server
) なので明示的な設定は必要ありません。

## [devcontainer.json](https://github.com/haruhikonyan/rails-devcontainer/blob/rails6.1.0-and-mysql/.devcontainer/devcontainer.json)
- [postCreateCommand](https://github.com/haruhikonyan/rails-devcontainer/blob/62c63c752d9a0b6af74a00e0abeb73f8693cb19c/.devcontainer/devcontainer.json#L18)  
見ての通りコンテナ作成時に Rails 起動に必要なことをすべてやっています。
- [initializeCommand](https://github.com/haruhikonyan/rails-devcontainer/blob/62c63c752d9a0b6af74a00e0abeb73f8693cb19c/.devcontainer/devcontainer.json#L19-L20)  
ぱっと見良くわからないコマンドなのですがやっていることは単純でコメントにもあるとおり、`.devcontainer/.env` が無ければ、`.devcontainer/.env.sample` をコピーしています。
docker-compose.yml で [env_file](https://github.com/haruhikonyan/rails-devcontainer/blob/62c63c752d9a0b6af74a00e0abeb73f8693cb19c/.devcontainer/docker-compose.yml#L18-L19) を設定しているので起動時のエラー防止です。

# カスタマイズ方法

## 自分で rails new したい
master リポジトリには rails new できる状態に各所コメントアウトした状態をコミットしてます。

- リポジトリクローン

```bash:ホスト側手順
$ git clone https://github.com/haruhikonyan/rails-devcontainer -b master
$ cd rails-devcontainer
$ code .
```
- DB のコンテナ設定
使いたい DB のコンテナを設定します。
mysql か postgrsql であれば [コチラ](https://github.com/haruhikonyan/rails-devcontainer/blob/master/.devcontainer/docker-compose.yml#L31-L45) のどちらかのコメントアウトを外せば設定完了します。
[volume のコメントアウト](https://github.com/haruhikonyan/rails-devcontainer/blob/master/.devcontainer/docker-compose.yml#L50)も忘れずに
- rails new
devcontainer を立ち上げ、`rails new` を実行します。

```bash:実行例
$ gem install rails
$ rails new .
```

- DB の gem 追加
デフォルトの sqlite ならそのままで良いと思います。
mysql なら [こんな具合に](https://github.com/haruhikonyan/rails-devcontainer/blob/ea3cab5d61baab0d0855ef51364ae86ed1176375/Gemfile#L9-L10)

- database.yml 修正
デフォルトだと sqlite の設定になっているかと思うので、それ以外の DB を選択した場合は設定変更が必要です。
環境変数を利用する場合は docker-compose.yml なども一緒に変更しましょう。
 
- devcontainer 各種設定
どれも必須ではありませんが好みに合わせて諸々自動起動するように devcontainer の設定を整えていきましょう。
    - [webpack 用コンテナ](https://github.com/haruhikonyan/rails-devcontainer/blob/f30106d9e7b18bb0048f4bec5e836338b2c149c2/.devcontainer/docker-compose.yml#L26-L30) の設定  
    [rails コンテナの environment](https://github.com/haruhikonyan/rails-devcontainer/blob/f30106d9e7b18bb0048f4bec5e836338b2c149c2/.devcontainer/docker-compose.yml#L17) の設定も忘れずに。
    - [コンテナ作成時の実行コマンド](https://github.com/haruhikonyan/rails-devcontainer/blob/master/.devcontainer/devcontainer.json#L18) の設定
- devcontainer rebuild
上記で設定した項目反映させるために devcontainer を rebuild します。
左下の `><` みたいな  ボタンから `Remote - Containers Rebuild container` を選択すれば全てのコンテナが再作成されます。
- 起動
あとは起動するだけです！
[コンテナ作成時の実行コマンド](https://github.com/haruhikonyan/rails-devcontainer/blob/master/.devcontainer/devcontainer.json#L18) をすべて設定していれば依存ライブラリのダウンロードと DB の作成、マイグレーションまで自動で終わるはずです。
設定してない場合はコンソールから手で実行しましょう。

```bash:起動
# rails s -b 0.0.0.0
```
## 既存の Rails プロジェクトで使いたい
基本的には上記の `rails new` する場合と同じで、`rails new` はせず、既存リポジトリに [.devcontainer ディレクトリとファイル一式](https://github.com/haruhikonyan/rails-devcontainer/blob/master/.devcontainer) を作成し、devcontainer を起動するだけです。


---

※この記事の内容は一部 [WESEEK Tips wiki](https://tips.weseek.co.jp/) に投稿された記事の転載です。
Tips wiki では、IT企業の技術的な情報やプロジェクトの情報を公開可能な範囲で公開してます。