【M1 mac 】Docker Rails + MySQL【環境構築〜Hello World】

## はじめに

DockerでRuby on rails + Mysqlの環境を作っていきます。

環境構築の仕方を知らないとDockerは書けないと思うので、Docker上で動かして環境構築しながらDockerfile及びdocker-composeの記述を追加していこうと思います。

`rails s`で起動確認できるところまでやってみます。

## Docker導入
[Docker Desktop for Apple silicon](https://matsuand.github.io/docs.docker.jp.onthefly/docker-for-mac/apple-silicon/)
ダウンロードって書いてるところわかる？

↓これはまだintel Macしか無理っぽい

```sh
$ brew install --cask docker
```
バージョン確認はこのコマンド

```sh
$ docker -v
```
MySQL8.xインストール

```
$ brew install mysql
```
どうしてもMySQLのインストールに詰まっている方は、`ver5.7`をインストールしましょう。

```sh
$ brew install mysql@5.7
$ brew link mysql@5.7 --force
```

導入はすばやく終了！

##環境構築

作業フォルダを作ってそこに`Dockerfile`と`docker-compose.yml`を作成します。
（後にこのフォルダにrailsファイルを生成していく形になります。）

`Dockerfile`にバージョンを記述してください。

ちょっと古い`2.4.6`を使ってみます。

```Dockerfile
FROM ruby:2.4.6
```

バージョンはこちらを参考にしました。
https://hub.docker.com/_/ruby

今回はバージョンを自分で決められるのでrailsファイル作る前からバージョン書いてますが、**もうすでにrailsファイルがある場合はgemfile見てそのバージョンを記述してくださいね**。

次に`docker-compose.yml`を書きます。

```docker-compose.yml
version: '3.8'
services:
  mysql: #ここはただの名前なのでdbとかdatabaseとかでもよい
    image: mysql:latest
　　　　　　　　platform: linux/x86_64　#M1の場合これ必要
    command: --default-authentication-plugin=mysql_native_password
    ports:
      - "3306:3306"
    volumes:
      - "./mysql-data:/var/lib/mysql"
    environment:
      MYSQL_ROOT_PASSWORD: root
  app: #コンテナの名前です
    build: .
    volumes:
      - ".:/app"
    ports:
      - "3000:3000"
    tty: true
    depends_on:
      - mysql
```

何を書いているのか上から順に説明しますね。

###version
docker-composeのバージョンについてはこちらを参考にしてください。
https://docs.docker.com/compose/compose-file/

###mysql
バージョンはこちらを参照してください。
https://hub.docker.com/_/mysql
環境構築で挫折しがちなバージョン8以降でやっていきます。
最新版がいいときは`mysql:latest`と書くこともできます。

※追記
これM1用で必要な部分

```yml
platform: linux/x86_64
```

mysql8以降を使う場合は必ずこれを記述してください。

```yml
command: --default-authentication-plugin=mysql_native_password
```

Mysql8から認証プラグインが`caching_sha2_password`になるので`mysql_native_password`に代えてあげる作業をしなければなりません。
ここがmysql8の環境構築でネックになる部分なのですが、Dockerではこの一行だけで解決してくれます。

なお、`5.7`を使う場合はこの記述は必要ありません。
↑で貼ったリンク先にその辺のことも書かれていて、サンプルコードもあります。

**ports**は`4306:3306`左側がローカルでDocker上のポートで紐づけています。
あとでsequel proに繋ぐやり方を書くのですが、左の`4306`を使うことになります。

**volumes**は、docker環境で作成したデータベースのデータの保存先を決めています。
`$ docker-compose up`したとき、指定している`mysql-data`という名前のフォルダがローカルに生成され、ここにdocker環境で作成したデータベースのデータが保存されます。
コンテナを消してしまったとしても前回作成したmysql-dataフォルダが残っていれば、再度コンテナを立ち上げたときに前回までのデータベースがそのまま生かされます。

`MYSQL_ROOT_PASSWORD: root`は、mysqlのusernameとpasswordを`root`に指定しています。
あとで`database.yml`をそのように修正しますね。

###app

`build: .`は動かすDockerfileのディレクトリを指します。

今回はdocker-compose.ymlと同じディレクトリにあるので`.`としてますが、

例えば`dockerフォルダの中のDockerfile.devというファイル名の場合`は、

```yml
build:
  context: ./docker
  dockerfile: Dockerfile.dev
```

と書きます。ということは、

```yml
build: .
#これは
build:
  context: .
  dockerfile: Dockerfile
#これの略だったんですね。
```

**volumes**は`".:/app"`左側の`.`がローカルのディレクトの位置です。
docker-compose.ymlがあるディレクトリにこれからrailsファイルを生成するので`.`になっています。
右側の`/app`はDockerのコンテナ内のappフォルダ内を指しています。
これでローカルのディレクトリとDockerコンテナ内のディレクトリを紐付けました。

**ports**はmysqlのときと同じで`"3000:3000"`はローカルのポート3000とDockerコンテナ内のポート3000を紐付けています。

`tty: true`これは書いといてください！

`depends_on: mysql`これは、appとmysqlは別々に起動している形になっているので、appがmysqlにアクセスできるようにしています。

##Docker動かしてみる！

一度これで動かしてみたいと思います。

```sh
$ docker-compose up
#中略
Recreating dockerrails_app_1 ... done
Attaching to dockerrails_app_1
```

こうなっていれば動いてます！

今動いてるdockerrails_app_1の中に入りたいですよね。
(`dockerrails`これはローカルのフォルダ名ですので各自違うと思います。)

もう一つターミナルを開いて次のコマンドを打ちます。

```sh
$ docker exec -it dockerrails_app_1 /bin/bash
```
これで入れます。

`docker-compose.yml`がルートディレクトリにあれば、

```sh
$ docker-compose exec app bash
```

これで入れます。

`# exit`で出れるので試してみてください。

`docker-compose.yml`はルートにないとめんどくさそうな気がするので毎回ルートに置いてます。

中に入れたら`lsコマンド`でどんなフォルダがあるのか確認してみてください。

```
# ls
app  bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
```

今作った`app`というフォルダ内に展開していきたいので、`cdコマンド`でappフォルダの中に入ります。

```
# cd app
```

これで`app#`に変わったと思います。

これDocker起動してdockerrails_app_1に入ったときに自動的にappフォルダ内に入りたいですよね。

**安心してください！**後でガッと実装しますので。

再度`lsコマンド`で確認してみると`Dockerfile`と`docker-compose.yml`がいるはずです。

いよいよrailsのファイル達を生成していくのですが、まずrailsをインストールするところから始まります。

```
app# gem install rails
```

インストールが完了したらrailsファイルを生成します。

mysqlの開発環境にするので、次のコマンドを流します。

```
app# rails new . -d mysql
```

これでファイルが生成されました。

まずは`database.yml`を修正します。

```database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  #下の二つを修正します。
  password: root
  host: mysql
```

`host: mysql`のmysqlは、docker-composeで指定した名前です。
`docker-compose.yml`で`mysql:`と命名して`depends_on: mysql`でマウントしましたよね。

さてrails起動してみますかー。

```
app# rails s -b 0.0.0.0
```

怒られるんですよね。

nodejsをインストールしないとダメなんです。

次の二つのコマンドを流してください。

```
app# apt-get update
app# apt-get install nodejs
```

インストール中(Y/n)を聞かれるので迷わず`Y`!と答えてください。

後でDockerfileの書き方で触れるので頭の片隅においといてください。

**色々と手動で環境構築しているのですが、最終的にすべて自動でしてくれるようにします！**

今やっていることはDockerfileに書いていくことになりますのでなんとなくでいいので覚えててください。

このままでも起動はするのですが、mysqlコマンドが使えないので`mysql-client`をインストールしてあげます。
インストール中(Y/n)を聞かれるので迷わず`Y`!

```
app# apt-get install mysql-client
```

データベース作ってあげてrailsを起動してあげます。

```
app# rails db:create
app# rails s -b 0.0.0.0
```

無事起動できましたか？？

##Dockerfile書くぞ！

はい本題ですw

Dockerを動かしてから環境構築するときどんなインストールコマンドを使ってきましたか？

```
gem install rails
apt-get update
apt-get install -y nodejs
apt-get install -y mysql-client
```

これらのコマンドを使って環境構築しました。

`-y`というのはインストール中に(Y/n)を聞かれた時にYと答えましたよね。そのyです。

これで止まることなく自動でYを選択してくれてどんどん進んでいきます。

これらのコマンドをDockerfileではこう書きます。

```Dockerfile
RUN gem install rails

RUN apt-get update && \
    apt-get install -y nodejs mysql-client
```
` \`バックスラッシュを使うことで改行して書くことができます。

次にbundle installも自動でするようにします。

```Dockerfile
WORKDIR /app
COPY Gemfile ./
COPY Gemfile.lock ./
RUN bundle install --jobs=4 --retry=3
```

`WORKDIR`でパスを指定することによって、Docker内での作業ディレクトリを指定することができます。

これでDockerを起動して`docker-compose exec app bash`を叩いたとき直接appフォルダ内に入ってくれるようになります。

そして、ローカルの`Gemfile`と`Gemfile.lock`をDockerコンテナ内のルートディレクトリ（先程`/app`に指定したので`/app`内）にコピーします。

その後`bundle install`を流しています。

パスは変数で指定した方が吉です。

↓で書いてみます。

こちらが今回作ったDockerfileです。

```Dockerfile
FROM ruby:2.4.6

ENV LANG=C.UTF-8

ENV WORK_DIR="/app" \
    CONTAINER_ROOT="./"

RUN apt-get update && \
    apt-get install -y nodejs mysql-client && \
    gem install rails

WORKDIR $WORK_DIR
COPY Gemfile Gemfile.lock $CONTAINER_ROOT
RUN bundle install --jobs=4 --retry=3
```

```docker-compose.yml
version: '3.7'
services:
  mysql:
    image: mysql:8.0.16
    command: --default-authentication-plugin=mysql_native_password
    ports:
      - "4306:3306"
    volumes:
      - "./mysql-data:/var/lib/mysql"
    environment:
      MYSQL_ROOT_PASSWORD: root
  app:
    build: .
    volumes:
      - ".:/app"
    ports:
      - "3000:3000"
    tty: true
    depends_on:
      - mysql
```

`Dockerfile`に`ENV LANG=C.UTF-8`を追加しています。

これはDockerコンテナ内で日本語をDockerさんに受け入れてもらうための記述です。

その下に`command: "rails s -b 0.0.0.0"`を記述するとrails起動までしてくれますがrailsを停止したときにDockerまで停止させてしまう（Dockerは停止させない設定が必要）のでDockerを起動させてから別ターミナルでrailsを起動するようにしましょ。

Dockerfileを書き換えたので再構築しましょう。`--build`オプションを使います。

```sh
$ docker-compose up --build
```

##よくあるエラー

`rails s -b 0.0.0.0`で下記のエラーが発生した場合、

```sh
A server is already running. Check /app/tmp/pids/server.pid.
```

`ローカルの作業ディレクトリ/tmp/pids/server.pid`を削除すれば解決します。

Dockerコンテナ立ち上げ時、毎回削除するようにできますが今回の記事では対象外とします。

##Sequel Proで接続
<img width="472" alt="スクリーンショット 2019-05-30 23.44.10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/399745/0259506f-e0b6-c5da-bb5b-a9ad2d384a0e.png">

これで繋がります。

Passwordの部分は`root`ですね。

##おわり

このくらいのボリュームなら緊急時でも何も見ずにプリッと書けますよね。(そんな緊急時があるのか？)

でもこれは基礎の「き」なのでコンテナのサイズが大きかったりします。。。

例えばベースイメージに`Alpine Linux`を使ったりすると実は半分のサイズになったりします。

<img width="614" alt="スクリーンショット 2019-05-30 23.48.18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/399745/5d4f7de2-e482-e61d-64a4-011a09f27bdd.png">

上がalpineで同じ環境内容で動かしたものになります。

下がこの記事で書いたDockerコンテナです。

alpineにするとまた環境構築のコマンドが違ってくるのでそちらに関して書こう・・・と思ったのですが、もうすでに詳しく書かれている記事があるのでやめときます。

https://note.mu/ho_m_ie/n/n9f0a6756446a

有料ですが500円でDocker書けるようになるならコスパよすぎです。

はい、寝ますおやすも