初心者がDockerで開発環境構築してみた（Rails&MySQL）

# 
### はじめに
現在、就職活動中のプログラミング初学者です。
開発環境にDockerを取り入れる為、いろいろ勉強して自分用のメモとしてまとめておりましたが、折角なら同じ初学者の方の役に立てればと思い投稿させて頂きます。既に多くの方がDockerの環境構築に関する記事を書かれており、そういった記事を参考にさせて頂きながら、メモとしてまとめていましたので、内容はそんなに目新しものではありません。予めご了承ください。また、誤った表現などがありましたらご指摘頂けると幸いです。

## 開発環境

- Ruby 2.6.3
- Rails 5.2.3
- MySQL 5.7
- MacOS 10.15.5

## 前提条件
- [Dockerhub](https://hub.docker.com/)のアカウントを作成している。
- [Docker for Mac](https://hub.docker.com/editions/community/docker-ce-desktop-mac)をインストールしている。
- Dockerの基礎はある程度抑えている。

Dockerの基本的なことはこちらの記事が参考になります。
[いまさらだけどDockerに入門したので分かりやすくまとめてみた](https://qiita.com/gold-kou/items/44860fbda1a34a001fc1)

## 対象読者
今回は開発環境を構築することを目標とする為、Dockerに関する基礎的な部分はある程度抑えている方とします。

## Railsの開発環境構築
DockerでRailsの開発環境を構築するには以下のファイルが必要になります。

- Dockerfile
- Gemfile
- Gemfile.lock
- docker-compose.yml

### 1.作成するアプリ名のディレクトリを作成する。
まずは、上記のファイルを作成するディレクトリを作成します。

```
#ターミナル
$ mkdir my-app
```
### 2.作成したディレクトリに移動する。
```
$ cd my-app
```
### 3.Dockerfileを作成する。
1.で作成したディレクトリ内にDockerfileを作成します。

```
$ touch Dockerfile
```
## Dockerfileとは？

> Dockerでは、dockerhubで公開されているimageを元にコンテナを起動することができるが、必要なパッケージや各種の設定を含んだDocker imageを自分で作成して使用したい場合が出てくる。その場合にDockerfileを用意して、それを元にDocker imageを作成することができる。
> Dockerfileにはベースのimageに対して実行する内容を記述する。


### 4.Dockerfileに記述する。

```Dockerfile:Dockerfile
# ベースイメージ　今回はrubyのimage。
FROM ruby:2.6.3

# 必要なパッケージのインストール
RUN apt-get update -qq && \
    apt-get install -y build-essential \ 
                       libpq-dev \        
                       nodejs           

# 作業ディレクトリの作成、設定
RUN mkdir /my-app

#作業ディレクトリを指定している
WORKDIR /my-app

# ホスト側（ローカル）のGemfileを追加する（ローカルのGemfileは別途事前に作成しておく）
COPY ./Gemfile /my-app/Gemfile
COPY ./Gemfile.lock /my-app/Gemfile.lock

# Gemfileのbundle install
RUN bundle install
COPY . /my-app
```

### 5.Gemfileを作成する。
```
$ touch Gemfile
```

### 6.Gemfileに記述する。

```ruby:Gemfile
source 'https://rubygems.org'
gem 'rails', '5.2.3'
```

### 7.空のGemfile.lockを作成する。
```
$ touch Gemfile.lock
```
### 8.docker-compose.ymlを作成する。
```
$ touch docker-compose.yml
```
## Docker Composeとは？
> Dockerでは１つのコンテナには、１つのアプリケーションのみを入れておき、複数のコンテナを組み合わせてサービスを構築するという方法が推奨されている。
> Docker Composeは、複数のコンテナで構成されるアプリケーションについて、Docker imageのbuildや各コンテナの起動・停止などをより簡単に行えるようにするツール。

## docker-compose.ymlとは？
> Docker Composeを使用する際に必要となるファイルのこと。

docker-compose.ymlには以下の内容を定義します。

- Docker imageをbuildするための情報（使用するDockerfile、image名など）
- コンテナ起動するための情報（ホストとの共有ディレクトリ設定やポートフォワードなどの起動オプションなど）
- 使用するDockerネットワーク　など

### 9.docker-compose.ymlに記述する。

```yml:docker-compose.yml
version: '3' # Dockerのバージョンを指定
services:
  db: # データベースサーバ用のコンテナの設定を記述
    image: mysql:5.7 # コンテナで使用するイメージ名を記述
    environment:
      MYSQL_ROOT_PASSWORD: password # 任意のパスワードを設定
      MYSQL_DATABASE: root # 任意のデータベース名を設定
    ports:
      - "3306:3306" # ホストの3306ポートとコンテナの3306ポートを接続する。
    volumes:
      - ./db/mysql/volumes:/var/lib/mysql #ホストの./db/mysql/volumesをコンテナ内の/var/lib/mysqlにマウントする。Dockerのコンテナと、ローカルを同期している。コンテナを削除してもデータは永続化する。

  web: # アプリケーションサーバ用のコンテナの設定を記述
    build: . # docker-compose.ymlと同じ階層にあるDockerfileを使ってimageをbuildするための記述
    command: bundle exec rails s -p 3000 -b '0.0.0.0' # コンテナ立ち上げ時に起動するコマンド。railsを実行する。

    volumes:
      - .:/my-app # 作業ディレクトリをコンテナ内の/my-appにマウントする。
    ports:
      - "3000:3000" # ホストの3000ポートとコンテナの3000ポートを接続する。
    depends_on:
      - db # 依存関係を表している。dbが起動してからwebが起動するという意味。
```
### 10.コンテナを起動し、コンテナ内でrails newを実行する。
```
$ docker-compose run web rails new . --force --database=mysql --skip-bundle
```
`run`コマンドは、imageの構築から、コンテナの構築・起動まで行います。
また、`run`コマンドを介して引数で指定したサービスのコンテナ内でコマンドを実行できます。つまり、ここでは生成されたコンテナ内で`rails new`を行っています。
`--force`オプションはファイルが存在する場合に上書きで作成するためのオプション。
`--database=mysql`オプションは使用するデータベースの指定をするためのオプション。
`--skip-bundle`オプションは`bundle install`をスキップするためのオプション。

### 11.database.ymlを修正する。
`rails new`で作成されたデータベースの接続設定ファイルにはホスト名とパスワードが設定されていません。railsアプリケーションがデータベースに接続できるようにするため、database.ymlを修正します。

```yml:database.yml

default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password # docker-compose.ymlのMYSQL_ROOT_PASSWORD
  host: db # docker-compose.ymlのservice名
```

### 12.buildを実行する。

```
$ docker-compose build
```
ここでbuildを実行する理由は10.で実行したコマンドによりGemfileの内容が書き換わっている為です。

### 13.Dockerを起動する。
```
$ docker-compose up -d # docker-compose.ymlを参照してコンテナが一斉起動する。
```
注意点としてここで実行する`docker-compose`コマンドはdocker-compose.ymlを元に実行が進むので、docker-compose.ymlがあるディレクトリ上で実行する必要があります。

### 14.データベースを作成する。
`docker-compose run web`をコマンドの先頭につけることでコンテナ内でコマンドを実行できるので、

```
$ docker-compose run web rails db:create
```
を実行すれば、データベースが作成できます。

要は、`docker-compose run web`に続けていつものローカルで開発している時のコマンドが使えます。

では、[localhost:3000](http://localhost:3000/)に接続してみましょう。

以下の画面が表示されれば完了です。

![](https://i.imgur.com/AmMPbge.jpg)

## gemを入れてbundle installしてみる。
開発環境が整ったので試しにgemを入れてbundle installしてみます。

```Gemfile:Gemfile
gem 'devise'
```
```
$ docker-compose run web bundle install
```
そして、

```
$ docker-compose run web rails g devise:install
```
しかし以下のようなエラーが出ました。

```
Could not find bcrypt-3.1.15 in any of the sources
Run `bundle install` to install missing gems.
```
`bundle install`したはずなのに。。。
これは12.のところと同じ現象です。つまり、ローカルで書き換えたGemfileがコンテナ内のGemfileに反映されていない。ここでは再度、`docker-compose build`を行う必要があります。理由はDockerfileを見れば分かります。

```Dockerfile:Dockerfile

COPY ./Gemfile /my-app/Gemfile
COPY ./Gemfile.lock /my-app/Gemfile.lock
```
Dockerfile内でimageをbuildする際にローカル側のGemfileをコンテナ側にコピーしていました。ですから、現状ローカルのGemfileの変更はコンテナのGemfileには反映されていない訳です。これを反映させる為に、再度`docker-compose build`を行う（imageをbuildし直す）必要があるという訳です。
では、再度`docker-compose build`を実行します。

```
$ docker-compose build
```
もう一度、エラーが出たコマンドを試します。

```
$ docker-compose run web rails g devise:install
```
今度は上手く行きました。

## しかし、gemを入れるたびに毎回`docker-compose build`するのは面倒臭い。。。
という訳で、毎回`docker-compose build`をしなくてもいいようにこちらの記事を参考にさせてもらい、`docker-compose.yml`を編集します。
[docker-composeでRailsのGemを更新する時、docker buildするのを回避したい](https://qiita.com/neko-neko/items/abe912eba9c113fd527e)

```yml:docker-compose.yml
version: '3'
services:
  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: root
    ports:
      - "3306:3306"
    volumes:
      - ./db/mysql/volumes:/var/lib/mysql
      
  web:
    build: . 
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/my-app
      - gem_data:/usr/local/bundle # ローカルのgem_dataをコンテナ内の/usr/local/bundleにマウントする。
    ports:
      - "3000:3000"
    depends_on:
      - db
      
volumes: # 上記のvolumesのgem_dataはローカルのものを使うことを指定している。
  gem_data:
```
これで、gemをインストールするたびに`docker-compose build`をしなくてもよくなりました。

## binding.pryをしたい時
docker-compose.ymlに以下を記述します。

```yml:docker-compose.yml
web:
  tty: true
  stdin_open: true
```

## SequelProでデータベースを可視化する。
私はローカルで開発を行う際、SequelProを使用してデータベースを可視化していましたが、コンテナの仮想環境下では現状SequelProでデータベースを可視化することはできません。
docker-compose.ymlを以下の様に書き換えSequelProでデータベースを可視化できるよう設定していきます。

```yml:docker-compose.yml
services:
  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: root
    ports:
      - "4306:3306" # ホストの4306ポートをコンテナ内の3306ポートに接続するよう書き換える。
    volumes:
      - ./db/mysql/volumes:/var/lib/mysql
```
次に、sequelpro起動時のモーダル内のタブを標準タブに切り替え、下記の通りに入力します。

```
名前: defaultのまま
ホスト: 127.0.0.1    # 自分自身を表すIPアドレス
ユーザー名: root　　  # database.ymlに記載のユーザー名
パスワード: password  # database.ymlに記載のパスワード
データベース:　空欄のまま
ポート: 4306　　　　  # docker-compose.ymlに記載したlocal側のports番号
```
![](https://i.imgur.com/8ajf9aZ.png)

これでdocker環境下でもデータベースが可視化できるようになります。

## 参考記事・参考動画
今回Dockerを学習するにあたり、多くの方の記事を参考にさせて頂きました。
ありがとうございました。
[docker-compose コマンドまとめ](https://qiita.com/wasanx25/items/d47caf37b79e855af95f)
[docker-compose up とか build とか start とかの違いを理解できていなかったのでまとめてみた。](https://qiita.com/tegnike/items/bcdcee0320e11a928d46)
[既存のRailsアプリをDockerコンテナで動かす方法+sequel proによるDBコンテナ可視化](https://qiita.com/y-suna/items/e52b3af1d80c52b66b31)
[Dockerを使用したRails開発①(環境構築)](https://qiita.com/Nishi53454367/items/aee4cf0c346bc115be99)
[【rails環境構築】docker + rails + mysql で環境構築（初心者でも30分で完了！）](https://www.youtube.com/watch?v=Fq1PH0Gwi8I&t=307s)