Rails6 + ruby3.0 + mysql8.0 + webpacker + puma + docker による開発環境の構築

本記事は開発環境の作成の記録を残すためのものです。
dockerによる開発環境を整えるのは初めてのため間違っていたり、より良い方法などがあればコメントにてアドバイスを頂けると幸いです。

## 目次
- 目標
 - セットアップする構成について
- 作業
 - 環境構築
- 感想
## 目標
今回作成する構成の目標は以下の通りです。

- Rails 6.1.1
- Ruby  3.0.0
- Mysql 8.0.26
- webpacker

これらをdockerによりセットアップしていきます。
状況としてはすでに作成したプロジェクトをdockerに置き換えることとします。

## 作業
実際の作業内容です。
手順は以下の通りです。

1. rails
 - Dockerfile
 - docker-compose.yml
 - 起動確認

2. mysql
 - docker-compose.yml
 - 起動確認
 
3. 全体の接続
 - docker-compose.yml
 - database.yml
 - 起動確認

### 1. rails
#### Dockerfile
rubyの公式イメージを利用し, railsのイメージを作成する

```Dockerfile:Dockerfile
# 1
FROM ruby:3.0.0

# 2
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
  && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
  && apt-get update -qq \
  && apt-get install -y yarn \
  && apt-get install -y nodejs npm && npm install n -g && n 12.0.0 \
  && apt-get install -y vim

# 3
WORKDIR /app

# 4
COPY Gemfile .
COPY Gemfile.lock .
COPY package.json .
COPY yarn.lock .
RUN bundle install
RUN yarn install

# 5
COPY . /app

# 6
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]

EXPOSE 3000
CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0"]
```

```sh:entrypoint.sh
#!/bin/bash
set -eu
rm -f tmp/pids/server.pid
exec "$@"
```

**\#1**

```Dockerfile
FROM ruby:3.0.0
```
利用するversionのイメージを取得

**\#2**

```Dockerfile
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
  && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
```
rubyのイメージへyarnパッケージ管理ツールをインストール

```Dockerfile
RUN apt-get update -qq \
    && apt-get install -y yarn \
    && apt-get install -y nodejs npm && npm install n -g && n 12.0.0 \
    && apt-get install -y vim
```
rubyのイメージへ `yarn`, `nodejs`, `vim` をインストール

```
apt-get install -y nodejs npm && npm install n -g && n 12.0.0
```
nodejsのバージョンを指定
(yarnで追加したパッケージのnodejsバージョンエラー対応)


**\#3**

```Dockerfile
WORKDIR /app
```
イメージ内で以下の作業を行う作業ディレクトリを指定

**\#4**

```Dockerfile
COPY Gemfile .
COPY Gemfile.lock .
COPY package.json .
COPY yarn.lock .
RUN bundle install
RUN yarn install
```

gem, yarn のライブラリ, パッケージをインストール

**\#5**

```Dockerfile
COPY . /app
```
その他ファイルをイメージ内の`/app`へコピー

**\#6**

```Dockerfile
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
```

`RUN chmod +x /usr/bin/entrypoint.sh` は実行権限を与えるコマンド
`ENTRYPOINT` はdockerのエントリーポイントを上書きするコマンド

以上でrailsのイメージの作成終了

#### docker-compose.yml
railsイメージのみでdocker-composeによるコンテナの立ち上げを試す

```yml:docker-compose.yml
version: '3.8'
services:
  app:
    build:
      context: .
      dockerfile: .
    environment:
      - EDITOR=vim
    command: bundle exec rails server -p 3000 -b '0.0.0.0'
    volumes:
      - .:/app
      - /var/tmp
    ports:
      - '3000:3000'
    stdin_open: true
    tty: true
```

`command: bundle exec rails server -p 3000 -b '0.0.0.0'` にて`-b '0.0.0.0'`によるバインドをしないとlocalhostでの確認が取れなくなるため注意

#### 起動確認
railsの起動を確認

```command
docker-compose build && \
docker-compose up
```
http://localhost:3000

### 2. mysql
#### docker-compose.yml

```yml:docker-compose.yml
version: '3.8'
services:
  db:
    image: mysql:8.0
    ports:
      - '3306:3306'
    volumes:
      - ./docker/mysql/conf.d:/etc/mysql/conf.d
      - mysql:/var/lib/mysql
    restart: always
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: 1
      TZ: "Asia/Tokyo"
```

```/docker/mysql/conf.d/mysql.cnf
[mysqld]
default_authentication_plugin = mysql_native_password
skip-host-cache
skip-name-resolve

character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
init-connect = SET NAMES utf8mb4
skip-character-set-client-handshake

[client]
default-character-set = utf8mb4

[mysqldump]
default-character-set = utf8mb4

[mysql]
default-character-set = utf8mb4
```
説明は以下の記事にまとめています
[dockerでmysql8.0を利用する最小確認構成](https://qiita.com/naok1207/items/7d8aa07e60d4d6619ea7)

#### 起動確認
mysqlのコンソールへの接続確認

```
docker-compose up -d && \
docker-compose run db mysql -h db -u root
```

### 3. 全体の接続
上記で起動を確認したrailsとmysqlにwebpackerを加えた全体の接続を行う

#### docker-compose.yml
```yml:docker-compose.yml
version: '3.8'
services:
  app:
    build:
      context: .
      dockerfile: .
    environment:
      - EDITOR=vim
      - WEBPACKER_DEV_SERVER_HOST=webpacker
      - DB_HOST=db
    command: bundle exec rails server -p 3000 -b '0.0.0.0'
    volumes:
      - .:/app
    ports:
      - '3000:3000'
    depends_on:
      - db
    stdin_open: true
    tty: true

  db:
    image: mysql:8.0
    ports:
      - '3306:3306'
    volumes:
      - ./docker/mysql/conf.d:/etc/mysql/conf.d
      - mysql:/var/lib/mysql
    restart: always
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: 1
      TZ: "Asia/Tokyo"

  webpacker:
    build: .
    command: ./bin/webpack-dev-server
    ports:
      - '3035:3035'
    depends_on:
      - app
    volumes:
      - .:/app
    environment:
      - WEBPACKER_DEV_SERVER_HOST=0.0.0.0

volumes:
  mysql:
```
`environment` : 環境変数の定義
`volumes` : 永続化するデータの定義
`depends_on` : 接続するコンテナの定義 

**app**
environmentとvolumesとdepends_onの定義を追加
`WEBPACKER_DEV_SERVER_HOST=webpacker` : webpack-dev-serverが起動しているhostを指定
`DB_HOST=db` : mysqlが起動しているhostを指定, databese.ymlで用いる

**db**
特に変更なし

**webpacker**
webpackerにはrailsと同じイメージが利用される
environmentとvolumesを定義
appと共通のボリュームを用いることで共通のファイルを変更した際にhotreloadされ, app側へ変更が反映される

#### database.yml

```yml:database.yml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  charset: utf8mb4
  pool: <%= ENV.fetch('RAILS_MAX_THREADS', 5) %>
  port: <%= ENV.fetch('MYSQL_PORT', 3306) %>
  username: <%= ENV.fetch('MYSQL_USERNAME', 'root') %>
  password: <%= ENV.fetch('MYSQL_PASSWORD', '') %>
  host: <%= ENV.fetch('DB_HOST', '') %>
```

`environment`で指定した`DB_HOST`をhostに指定する
ユーザ名, パスワードを指定したい場合には`app`と`mysql`にて`environment`に`MYSQL_USERNAME`と`MYSQL_PASSWORD`を指定する

#### 起動確認
railsの起動を確認

```command
docker-compose build && \
docker-compose run app bundle exec rails db:create && \
docker-compose run app bundle exec rails db:migrate && \
docker-compose up
```

これで今回の開発環境の構築は終了

## 感想
これまではあまり理解せずにdockerを利用したりしなかったりしていたので改めていろいろなエラーに遭遇しながら環境を構築してみたところ少し景色が変わったように感じたのでとても面白かった. 

## 参考
https://qiita.com/me-654393/items/ac6f61f3eee66380ecd7
https://zenn.dev/tmasuyama1114/articles/rails-docker-6x-how-to

## webpackerの処理について自分の考えを補足
こちらに関しては自分の考えになっており確認等をしていないためアドバイスいただけると幸いです.

```yml
app:
 volumes:
   - .:/app

webpacker:
 volumes:
   - .:/app
```
これらの示すものとしては

- ローカルのソースコード
- appコンテナ内のソースコード
- webpackerコンテナ内のソースコード

です.
これらの3つのソースコードを共通化により`webpack-dev-server`を実現しています.
処理の流れは以下になります.

1. ローカルのソースコードを編集
2. ローカルのソースコードの変更がappコンテナ, webpackerコンテナのソースコードに反映される
3. webpackerコンテナ内でソースコードの変更が検知されpacksが書き換えられhot reloadのイベントが起こる
4. webpackerコンテナ内のソースコード(packs)の変更がローカルのソースコードに反映される
5. ローカルのソースコードに反映された変更がさらにappコンテナ内のソースコードに反映される
6. hot reloadのイベントをappコンテナ内で起動しているrails serverで検知されページが更新される

このような動きをしているのかな？と考えています.