Ruby on Railsの習得のためタスクリスト的なの作ってみる②〜Rails7.0+React18.0+ MySQL8.0 Docker開発環境構築編〜

今回は開発環境構築をします。
前回構成を書いた通り、Rails + React + MySQL のDocker環境を作成します。

リポジトリの全体像は、以下のような感じ。
各ソースのディレクトリをコンテナにマウントします。

```
backend/ 
    ├ rails #バックエンドのソース
    └ Dockerfile
frontend/ 
    ├ react #フロントエンドのソース
    └ Dockerfile
docker-compose.yml
```

※[ディレクトリ構成図を書くとき助かる記事はこちら](https://qiita.com/paty-fakename/items/c82ed27b4070feeceff6)

## 前提
macOS Monterey バージョン12.4
[Docker Desktop]()はインストール済み

## Docker構築

Docker自体をよく知らないという方は[先人の知見を参考にしてください](https://qiita.com/etaroid/items/b1024c7d200a75b992fc)!

※そんなにわからなくていいや〜という方向けの超々ざっくり説明
→コンテナっていう塊の中でrubyとかnodeとかがそれぞれ動いていて、docker-compose.ymlが複数のコンテナの置き方とか繋ぎ方とかの指示書、Dockerfileは各コンテナの中に入れておくものとか起動した時に何をするかとかの指示書。これらの指示書を共有するとみんな同じ内容の環境で開発できるね！プロジェクトごとに使いたいバージョンとか変わってもコンテナを変えればいいだけだね！便利！っていう技術。

Docker公式のRailsのサンプルは[ちょっと古いけどこれ](https://github.com/docker/awesome-compose/tree/master/official-documentation-samples/rails/)。（2023-03現在ruby2.5）

参考にした記事は以下。
[Rails6.1 + MySQL + ReactでのDocker環境を構築する](https://qiita.com/ryouzi/items/c8c3fa4a5e16e6710d6c)
[Rails 7 + MySQLの環境構築をDocker composeで作る](https://qiita.com/croquette0212/items/7b99d9339fd773ddf20b)

※以下、「achievelist」は今回作るアプリ名です。好きな名前をつけてください。念の為。

### バックエンド用のコンテナを作成する（Rails7.0）

`/achievelist/tmp/pids/server.pid`が存在するとサーバー立ち上げに失敗するから`entrypoint.sh`を使って起動時に削除しないといけないらしいです。試しに削除せずにコンテナ起動してみたら、確かに以下のようなメッセージが表示されて起動に失敗しました。
```
backend_1  | A server is already running. Check /achievelist/tmp/pids/server.pid.
```

対策は、Dockerfileと同階層に以下のような`entrypoint.sh`を設置して、Dockerfile内で`/usr/bin/`にコピーし、[ENTRYPOINT](https://docs.docker.jp/engine/reference/builder.html#entrypoint)として設定することです。
```entrypoint.sh
#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /achievelist/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"
```

他は普通に必要な工程を書いてDockerfileを作成します。

```Dockerfile
FROM ruby:3.2.1

WORKDIR /achievelist
COPY ./rails/achievelist/Gemfile /achievelist/Gemfile
COPY ./rails/achievelist/Gemfile.lock /achievelist/Gemfile.lock
RUN bundle install
COPY ./rails/achievelist /achievelist

# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]

EXPOSE 3000

CMD ["rails", "server", "-b", "0.0.0.0"]
```

まずはこれを動かすだけの`docker-compose.yml`を書いて、、
```docker-compose.yml
version: '3.8'

services:
  backend:
    build:
      context: ./backend/
      dockerfile: Dockerfile
    stdin_open: true
    tty: true
    volumes:
      - ./backend/rails/achievelist:/achievelist
    command: bash -c "rm -rf tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    ports:
      - "3000:3000"
    environment:
      TZ: Asia/Tokyo
```

実行！
```sh
$ docker-compose up
Recreating achievelist_backend_1 ... done
Attaching to achievelist_backend_1
backend_1  | => Booting Puma
backend_1  | => Rails 7.0.4.2 application starting in development
backend_1  | => Run `bin/rails server --help` for more startup options
backend_1  | Puma starting in single mode...
backend_1  | * Puma version: 5.6.5 (ruby 3.2.1-p31) ("Birdie's Version")
backend_1  | *  Min threads: 5
backend_1  | *  Max threads: 5
backend_1  | *  Environment: development
backend_1  | *          PID: 1
backend_1  | * Listening on http://0.0.0.0:3000
backend_1  | Use Ctrl-C to stop
```
`http://localhost:3000/`にアクセスすると、無事Railsのロゴを見ることができました。

### DB用のコンテナを作成する（MySQL8.0）

こちらは公式のイメージをそのままdocker-compose.ymlに指定するだけ。DBデータは[永続化](https://nishinatoshiharu.com/docker-volume-tutorial/)しています。
※ユーザー名やパスワードは、環境設定ファイルとかに書いた方がいいです。

```docker-compose.yml
version: '3.8'

services:
  db:
    image: mysql:8.0
    volumes:
      - db_data:/var/lib/mysql
    ports:
      - "3306:3306"
    environment:
      - MYSQL_DATABASE=testdb
      - MYSQL_USER=root
      - MYSQL_PASSWORD=pass
      - MYSQL_ROOT_PASSWORD=pass
      - TZ=Asia/Tokyo
  backend:
    build:
      context: ./backend/
      dockerfile: Dockerfile
    stdin_open: true
    tty: true
    volumes:
      - ./backend/rails/achievelist:/achievelist
    command: bash -c "rm -rf tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    ports:
      - "3000:3000"
    environment:
      TZ: Asia/Tokyo
    depends_on:
      - db
volumes:
  db_data:
```

あと、depends_onっていう項目を今回知りました。
コンテナの依存関係を指定すると起動のタイミングをいい感じにしてくれるそうです。([参考記事](https://gotohayato.com/content/533/))

### フロントエンド用のコンテナを作成する（React18.0）

ここにReactのコンテナを載せていきます。
正直Reactは構築の手間もかからないしホスト機で直接動かせばよくない？みたいな気持ちもあるけどnodeのバージョンとかもあるしちゃんとコンテナ立てようっと。
デフォルトのポート番号が3000なのでそのままだとバックエンドと被っています。フロントのポート番号を3001とかにずらしてもいいんだけど、慣習的にサーバーサイドのポート番号は80みたいなところがあるので、ここは3000を譲ってもらうことにします。
~~本当はプロキシ通してSSL化して443ポートにすべきだけど面倒だからなし！~~

フロントのDockerfileは以下。nodeは最新で、19.7.0にしておきます。

```Dockerfile
FROM node:19.7.0-alpine
WORKDIR /usr/src/app
```

で、これも載せたdocker-compose.ymlが以下になります。

```docker-compose.yml
version: '3.8'

services:
  db:
    image: mysql:8.0
    volumes:
      - db_data:/var/lib/mysql
    ports:
      - "3306:3306"
    environment:
      - MYSQL_DATABASE=testdb
      - MYSQL_USER=root
      - MYSQL_PASSWORD=pass
      - MYSQL_ROOT_PASSWORD=pass
      - TZ=Asia/Tokyo
  backend:
    build:
      context: ./backend/
      dockerfile: Dockerfile
    stdin_open: true
    tty: true
    volumes:
      - ./backend/rails/achievelist:/achievelist
    command: bash -c "rm -rf tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    ports:
      - "80:3000"
    environment:
      TZ: Asia/Tokyo
    depends_on:
      - db
  frontend:
    build:
      context: ./frontend/
      dockerfile: Dockerfile
    stdin_open: true
    tty: true
    volumes:
      - ./frontend/react/achievelist:/usr/src/app
    command: sh -c "npm start"
    ports:
      - "3000:3000"
volumes:
  db_data:
```

これで、一通りできたので、`docker-compose up`すると、
`http://localhost:3000/`にアクセスするとReactの画面、
`http://localhost:80/`にアクセスするとRailsの画面が表示する状態になりました。
あとは、開発していくうちに困ったことがあれば改修していくことにします。

## GitHubに載せる

私はあらかじめリモートリポジトリを作成してcloneしたところに上記環境を載せる派ですが、もう作ってしまったものをgit管理したい人は、[git init してからリモートリポジトリを設定してpushしましょう](https://www.sukerou.com/2019/12/git-tipsgit.html)。

## 次回予告
次は、この環境の上でいよいよWebアプリ開発に入ります。まずはAPIを作ろう！ということで次回はバックエンド編です。そもそもRuby on Railsの学習がしたくて始めたので、ここがメイン。がんばるぞい！


(2023-03-07) docker-composeのdbのenvironmentの値にクオーテーションをつけていることにより想定したユーザー・パスワードの登録ができていなかったのでクオーテーションを外しました。