既存のRailsアプリをDockerコンテナで動かす方法+sequel proによるDBコンテナ可視化

# はじめに
新規アプリをDocker環境で開発するやり方はたくさんあるけど、既存アプリをDocker環境で構築するやり方は全然見当たらず、わりと苦労しました。
振り返ってみると新規アプリでの構築の仕方とあまり変わらないはずなのですが、色々エラー出て苦労したのでまとめておきます。

個々のコマンドの意味もできるだけ記載しました。
単なる環境構築だけでなく、DBコンテナのsequel proによる可視化やAPPコンテナでのbinding.pryの仕方、bundle install後にbuildし直さなくてもよい設定にする方法などもまとめました。

同じく既存のRailsアプリをDocker上で構築したい人の参考になれば幸いです。

内容について間違っていたら教えていただけると嬉しいです。

# 開発環境
- Ruby: 2.5.1
- Rails: 5.2.4
- MySQL: 5.6
- MacOS

# 前提
- Docker for Macはインストール済み
- Dockerについての基礎知識
- 今回は開発環境のみ

# 対象読者
作成した既存のアプリをDocker上で構築したい人

# 目次
- コンテナ起動までの大まかな流れ
- 実際の作業
- 番外編：sequel proによるDBコンテナの可視化
- 開発する上でのDockerコマンド
- おまけ
  - railsコンテナ上でbinding.pryをする方法
  - railsコンテナ上でbundle installした時に、変更内容をコンテナ上に反映させる方法

### コンテナ起動までの大まかな流れ
DockerfileにてRubyのベースイメージをもとにイメージを作成する
↓
作成したイメージをもとにdocker-compose.ymlでappコンテナを作成すると同時に、DBのコンテナのイメージを作成し、これらのコンテナを連携させる
↓
database.ymlを修正してappコンテナからdbコンテナへ接続できるように設定する

### 実際の作業
1. Dockerfileとdocker-compose.ymlの作成
2. Dockerfileの記載
3. docker-compose.ymlの記載
4. database.ymlの変更

### 1. Dockerfileとdocker-compose.ymlの作成

まず、開発しているアプリで、Dockerfileとdocker-compose.ymlを以下のように作成します。

```
アプリ名
  |- app
  |- bin
  |- config
      #略
  |- vendor
  - .gitignore
  - config.rb
  - Dockerfile           #追加
  - docker-compose.yml　　#追加
  - Gemfile
  - Gemfile.lock
      #略
```


### 2. Dockerfileの記載

続いてDockerfileの中身を書いていきます。
myprojectのところはコンテナ起動の際に作成するディレクトリ名なので、何でも大丈夫です
ただし、それ以降の記述でも随時書き換えてください

```Ruby:Dockerfile
FROM ruby:2.5.1

RUN apt-get update && \
    apt-get install -y mysql-client nodejs vim --no-install-recommends && \
    rm -rf /var/lib/apt/lists/*

RUN mkdir /myproject

WORKDIR /myproject

ADD Gemfile /myproject/Gemfile
ADD Gemfile.lock /myproject/Gemfile.lock

RUN gem install bundler
RUN bundle install

ADD . /myproject
```

詳細な説明は省きますが、ざっと説明すると

- ruby2.5.1をベースイメージとする
- コンテナ内で必要なコマンドをインストール
- myprojectというディレクトリを作成して基点にする
- Gemfileをコンテナ上にコピーした後、bundle install
- ローカルのディレクトリ、ファイルをコンテナ上にコピー

という感じかと思います。


### 3. docker-compose.ymlの記載

Rubyのコンテナは作成できるようになりました。
続いてこれをもとにアプリケーションのコンテナとデータベースのコンテナを作成し、それらのコンテナをリンクさせるための作業をしていきます。
docker-compose.ymlを以下のように記載します。
*mysqlは8.0以上だと認証方法が異なるようなので注意
https://qiita.com/yensaki/items/9e453b7320ca2d0461c7

```Ruby:docker-compose.yml
version: '2'
services:
  db:
    image: mysql:5.6
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: root
    volumes:
      - mysql-data:/var/lib/mysql    #データの永続化のために必要
    ports:
      - "4306:3306"  #両方3306でもok。詳細は下の「番外編：DBをsequel proで可視化したい」へ

  app:
    tty: true          #コンテナ上でbinding.pryするために必要
    stdin_open: true   #コンテナ上でbinding.pryするために必要
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/myproject    #ローカルのディレクトリをコンテナ上にマウント
      - bundle:/usr/local/bundle    #bundle installした後buildし直さなくてよくなる
    ports:
      - "3000:3000"
    links:
      - db

volumes:
  mysql-data:
  bundle:      #bundle installした後buildし直さなくてよくなる
```

要点は以下

- versionは2でも3でもいいと思いますが、使えるコマンドが違ってくるみたいです。
- servicesのところにdbとappがありますが、これらがそれぞれコンテナになります。
- appのコンテナのlinksにdbがあり、これによってappコンテナとdbコンテナが連携できるようになります
- 一番下のvolumesには永続化させたいデータを記載
- appコンテナ上でvolumes: - .:/myprojectとすることで、ローカルのディレクトリをマウントしている。
- dbコンテナ上でvolumes: -mysql-data:/var/lib/mysqlとすることでデータベースで変更されたデータを永続化。この記述がないと、コンテナを壊した時に変更したデータが消えてしまいます。
- bundle installとbinding.pryのための記述は本記事、最後のおまけを参照


### 4. database.ymlの変更

これまでの作業でappコンテナとdbコンテナを連携させる設定をしました。
最後にappコンテナからdbコンテナに接続するために、接続設定をします。
database.ymlの中身はおそらく初期設定のままだとこんな感じの記述になってるかと思います。

```Ruby:config/database.yml変更前
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password:
  socket: /tmp/mysql.sock
```

このままだとsocket通信でDB接続をするので、せっかく作成したdbコンテナが意味なくなってしまいます。
作成したdbコンテナに接続するために以下のように変更します。


```Ruby:config/database.yml変更後
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password:password
  host: db   #変更(docker-compose.ymlのservice名を記載)
```

usernameやpasswordは環境変数で設定している記事もいくつか見ますが、開発環境なら気にしなくてもいいかなと思いました。
本番環境ではちゃんと環境変数を設定します。

以上で準備ができました。
あとはターミナルで該当アプリのディレクトリまで移動して、docker-compose upと入力するだけです
（Gemfile.lockの中身を削除して空にしないとエラーになるかもしれません）
localhost:3000で接続確認して終了です！

今回はMySQLを使いましたが、他のDBでも可能なはずです(未検証)
＊ポート番号は変わるはずなのでご注意ください


## 番外編：DBをsequel proで可視化したい
今まで開発していた時、DBをsequel proで可視化していて便利だったのですが、docker上のDBも可視化したい！
という方に向けて以下に方法を記載します

docker-compose.ymlで以下のように記載していました
```Ruby:docker-compose.yml
db:
  ports:
    - "4306:3306"
```

これはホストが4306で接続した時にコンテナ上では3306に置き換えますという意味です。
準備はこれでokなのでsequel proで接続します。
sequel proを開いて、標準タブに切り替えます。
そこで以下の内容を入力します

```
名前: 任意、変えなくてもok
ホスト: 127.0.0.1
ユーザー名: root (database.ymlに記載のユーザー名)
パスワード: password (database.ymlにパスワード)
データベース: 空でok
ポート: 4306（docker-compose.ymlのportsに記載した左側）
```
ホストの127.0.0.1は自分自身を表すIPアドレス

これで見れるようになるはず！


## その他開発する上で必要なコマンド
docker上でrails g controllerやrails db:migrateなどを行う時は以下のようにコンテナを通して入力します
appの部分はdocker-compose.ymlで作成したコンテナ名のappのことです

```
docker-compose run --rm app 入力したいコマンド(例: rails db:migrate)
```

その他必要なコマンドは以下の記事参照
https://qiita.com/gold-kou/items/44860fbda1a34a001fc1

<br>
全てのコンテナやイメージを削除する場合はこちら

```
#全てのコンテナ停止
docker stop $(docker ps -q)

#全てのコンテナ削除
docker rm $(docker ps -q -a)

＃全てのイメージ削除
docker rmi $(docker images -q)
```

## おまけ

###bundle installしたい時
開発していく中で、gemを追加してbundle installしたい場面が出てくると思います
そのままやってもできるんですが、追加したgem内容がコンテナ内に反映されないため、イメージをbuildし直さなくてはなりません。
いくつか解決方法はあるみたいですが、今回はvolumeでマウントするという方法で解決しました。

```Ruby:docker-compose.yml
app:
  volumes:
    - bundle:/usr/local/bundle

   #中略

volumes:
  bundle:
```
参考記事
https://qiita.com/neko-neko/items/abe912eba9c113fd527e

### bindin.pryしたい時

binding.pryをするためには以下の記述を追加します

```Ruby:docker-compose.yml
app:
  tty: true
  stdin_open: true
```

# おわりに
dockerについて全くわからないところから環境構築するのはかなり大変でした
自分なりにまとめられて良かった
同じような状況の人の参考になれば幸いです

間違ってる場所があれば指摘していただけると幸いです

本番環境でDockerを使うのも苦労したので、そのうち開発環境との違いなどもまとめようかなと思います

# 参考記事
Dockerについての概要と色々なTIPSを知りたい場合は下記リンクがおすすめ
https://qiita.com/gold-kou/items/44860fbda1a34a001fc1

実際の作業で参考にした記事
https://qiita.com/azul915/items/5b7063cbc80192343fc0
https://qiita.com/Nishi53454367/items/aee4cf0c346bc115be99