丁寧すぎるDocker-composeによるrails5 + MySQL on Dockerの環境構築(Docker for Mac)

##はじめに
 Dockerの勉強も兼ねてrailsのアプリをコンテナに作ろうとしたが環境構築にアホみたいに時間がかかった。
 Dockerもrailsも（~~もはや初歩的なコマンドすら~~）不慣れなプログラマを救いたいので、手順を書きます。
 あとDockerはこれからrailsエンジニアになる人が合わせて使えるようになっておけばスキルとして加点になるので（新しめの技術なので供給量的にも）、勉強のきっかけになればと。

##この手順すらめんどくさいと思った方へ(2019-02-04追記)
[何もない状態からrailsサーバーとMySQLのコンテナを立ててくれるシェル](https://qiita.com/azul915/items/5b7063cbc80192343fc0#%E4%BD%95%E3%82%82%E3%81%AA%E3%81%84%E7%8A%B6%E6%85%8B%E3%81%8B%E3%82%89rails%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E3%81%A8mysql%E3%81%AE%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E3%82%92%E7%AB%8B%E3%81%A6%E3%81%A6%E3%81%8F%E3%82%8C%E3%82%8B%E3%82%B7%E3%82%A7%E3%83%AB2019-02-04%E8%BF%BD%E8%A8%98)
※真面目に作りを意識しながらやった方が今後応用が効くようになるのでできるだけ読んで欲しいです...

##環境（!!Rails5系で作成してます!!）
* Mac OS
* Ruby2.5.3
* Rails5.2.2
* MySQL5.7

-> Rails6系では、webpackが入った都合上、yarnのインストールが必要になります。

- [こちら](https://masaki.blog/rails6-on-docker/)のブログが私の記事を参考にしてくださっているので、6系については、そちらに移譲します(2020-02-19追記)
- [私の方でも6系のテンプレートを作成しました](https://github.com/azul915/rails6_docker_template)(2020-02-19追記)
- [zennにrails6の構築記事を書き直しました](https://zenn.dev/az/articles/6e2a421f7a32446d827f)(2020-11-21追記）

##前提(2019-02-04追記)
　そもそもDockerコンテナを使うメリットの一つに周辺環境との依存を避けることができることが挙げられる。だが、ローカル（ホスト側）の任意の場所に、アプリを作っていくフォルダ/ファイルを作り、それらをDockerのコンテナ内で利用したり、逆にコンテナ内のフォルダ/ファイルをローカルで扱いたいケースがある。このような場合など、ローカル（ホスト側）の一部のフォルダ/ファイルをコンテナ内のフォルダ/ファイルと同期することを**ボリュームマウント**するという。

![mount-overview.png](https://qiita-image-store.s3.amazonaws.com/0/181900/437efe8a-8d14-a31f-5043-6cdf4ab986f3.png)

　【画像・参考】[さわって理解する Docker 入門](https://www.ogis-ri.co.jp/otc/hiroba/technical/docker/part7.html)　より


以上を踏まえて、ローカル（ホスト側）のアプリの作業フォルダを`sampleApp`（各自で命名してください）とし、コンテナ側を`app_name`としている。`app_name`は`Dockerfile`や`docker-compose.yml`で使っているが、`app_name`から変更しなくてよい。（コピペでOK）


##ファイル構成（ホスト側つまりローカルが最終的にこういう構成になります）

```
sampleApp ---- Dockerfile
           |-- Gemfile
           |-- Gemfile.lock
           |-- README.md
           |-- Rakefile
           |-- app
           |-- bin
           |-- config ---- application.rb
           |           |-- boot.rb
           |           |-- cable.yml
           |           |-- credentials.yml.enc
           |           |-- database.yml
           |           |-- environment.rb
           |           |-- environments
           |           |-- initializers
           |           |-- locales
           |           |-- master.key
           |           |-- puma.rb
           |           |-- routes.rb
           |           |-- spring.rb
           |           |-- storage.yml
           |
           |-- config.ru
           |-- db
           |-- docker-compose.yml
           |-- lib
           |-- log
           |-- package.json
           |-- public
           |-- storage
           |-- test
           |-- tmp
           |-- vendor
```
##【１】プロジェクトの作業ディレクトリを作成、移動
`mkdir`でディレクトリを作成して`cd`で移動

```bash
$ mkdir sampleApp
$ cd sampleApp
```
##【２】作業ディレクトリでDockerfileを作成、中身の編集

主はvimで編集をしているが、`touch Dockerfile`して、エディタで編集してもOK。
Dockerfileは、Docker上で動作するコンテナの構成情報を記述するためのファイル
（この後【8】で実行してもらう、）`docker-build`コマンドによって、Dockerfileに記述されたインフラの構成情報をもとにしてコンテナのもととなるイメージを作成する。

Dockerfileの注意点として、`COPY`か`ADD`をするときにホスト側（左側）は必ず（Dockerfileを置いてあるディレクトリからの）相対パスなので注意（初心者向けに明示的な書き方をした。）

```bash
## touch Dockerfile してから　vi Dockerfileでもいいけど、カレントディレクトリ内に同じファイル名がなければ、作成&編集ができる
$ vi Dockerfile
```

```dockerfile:Dockerfile
# コピペでOK, app_nameもそのままでOK
# 19.01.20現在最新安定版のイメージを取得
FROM ruby:2.5.3

# 必要なパッケージのインストール（基本的に必要になってくるものだと思うので削らないこと）
RUN apt-get update -qq && \
    apt-get install -y build-essential \ 
                       libpq-dev \        
                       nodejs           

# 作業ディレクトリの作成、設定
RUN mkdir /app_name 
##作業ディレクトリ名をAPP_ROOTに割り当てて、以下$APP_ROOTで参照
ENV APP_ROOT /app_name 
WORKDIR $APP_ROOT

# ホスト側（ローカル）のGemfileを追加する（ローカルのGemfileは【３】で作成）
ADD ./Gemfile $APP_ROOT/Gemfile
ADD ./Gemfile.lock $APP_ROOT/Gemfile.lock

# Gemfileのbundle install
RUN bundle install
ADD . $APP_ROOT
```
##【３】Gemfileを作成、編集
　`touch Gemfile`で作成して、エディタで編集してもOK
GemfileはBunder用の設定ファイルでこれを元にgemの依存関係を示した、Gemfile.lockを作成する（【４】で空のGemfile.lockを作る）

```bash
# vimでGemfileの名前でファイル新規作成
$ vi Gemfile
```
```:Gemfile（コピペでOK）
source 'https://rubygems.org'
gem 'rails', '5.2.2'
```
##【４】空のGemfile.lockを作成
```bash
$ touch Gemfile.lock
```
##【５】docker-compose.ymlを作成、編集
ymlファイルを以下の例をコピペして作成、`MYSQL_ROOT_PASSWORD`と`MYSQL_DATABASE`で、任意のパスワード（【７】で同じものを設定する）とデータベース名を設定

```bash
$ vi docker-compose.yml
```
```docker-compose.yml
version: '3'
services:
  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: root
    ports:
      - "3306:3306"

  web:
    build: .
    command: rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/app_name
    ports:
      - "3000:3000"
    links:
      - db
```
##【６】rails newを実行する

```bash
$ docker-compose run web rails new . --force --database=mysql --skip-bundle
```
##【７】database.ymlを修正する
場所はここ -> `/sampleApp/config/database.yml`

```bash
## カレントディレクトリ（/sampleApp）配下を見てみると色々できていて、configも作成されている
$ ls -l

total 56
-rw-r--r--   1 author  staff   519  1 20 20:07 Dockerfile
-rw-r--r--   1 author  staff  2216  1 20 20:15 Gemfile
-rw-r--r--   1 author  staff     0  1 20 20:08 Gemfile.lock
-rw-r--r--   1 author  staff   374  1 20 20:15 README.md
-rw-r--r--   1 author  staff   227  1 20 20:15 Rakefile
drwxr-xr-x  10 author  staff   320  1 20 20:15 app
drwxr-xr-x   8 author  staff   256  1 20 20:15 bin
drwxr-xr-x  16 author  staff   512  1 20 20:15 config
-rw-r--r--   1 author  staff   130  1 20 20:15 config.ru
drwxr-xr-x   3 author  staff    96  1 20 20:15 db
-rw-r--r--   1 author  staff   261  1 20 20:09 docker-compose.yml
drwxr-xr-x   4 author  staff   128  1 20 20:15 lib
drwxr-xr-x   3 author  staff    96  1 20 20:15 log
-rw-r--r--   1 author  staff    67  1 20 20:15 package.json
drwxr-xr-x   9 author  staff   288  1 20 20:15 public
drwxr-xr-x   3 author  staff    96  1 20 20:15 storage
drwxr-xr-x  11 author  staff   352  1 20 20:15 test
drwxr-xr-x   5 author  staff   160  1 20 20:15 tmp
drwxr-xr-x   3 author  staff    96  1 20 20:15 vendor

```

```bash
## カレントディレクトリ（/sampleApp）にて
$ cd config
$ vi database.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名
```
##【８】Docker起動
`/sampleApp/config/`がカレントディレクトリなので、`cd ..`で一つ上の階層（`/sampleApp`）に移動すること

Why？
↓
この後実行する`docker-compose`コマンドは`docker-compose.yml`ファイルを探して実行が進むので、そのディレクトリ（今回なら、`/sampleApp`）上で実行しなければならないから

```bash
$ docker-compose build # コンテナをビルド

# -dオプションをつけてバックグラウンド実行するとこの後新しいシェルを立ち上げる必要がなくなる
$ docker-compose up # コンテナの一斉起動
```

##【９】DB作成
新しいterminalを開いて、`/sampleApp`で実行

```
$ docker-compose run web rails db:create
```

##【10】ブラウザでlocalhost:3000にアクセスしてサーバーの起動を確認

![スクリーンショット 2019-01-20 18.49.44.png](https://qiita-image-store.s3.amazonaws.com/0/181900/eca45046-eed1-4397-4bda-f1112e98fbb7.png)

## おまけ
####　サーバーを止める
```
$ docker-compose down
```
`Ctrl+C`で止めると、コンテナが残って次回起動時にエラーが出る
もしやっちゃったら、`tmp/pids/server.pid`を削除、再起動
再起動は`docker-compose up`でできる

####Dockerfileやdocker-compose.ymlの変更を反映、railsサーバーを再起動
```
$ docker-compose up --build
```

####bundle installなどのコマンドを実行したい
```bash

#docker-compose run {サービス名} {任意のコマンド}
$ docker-compose run web bundle install
```
####ローカルからMySQLコンテナに接続
```bash
$ mysql -u root -p -h localhost -P 3306 --protocol=tcp
```

### 追記(2019-01-24)：ホスト側でDockerfileおよびdocker-compose.ymlとソースを分けたい方へ
####ファイル構造（ホスト側）
```
sampleApp ---- Dockerfile
           |-- docker-compose.yml
           |
           |-- src -- |-- app
                      |-- bin
                      |-- config
                      |-- db
                      |-- lib
                      |-- log
                      |-- public
                      |-- storage
                      |-- test
                      |-- tmp
                      |-- vendor
                      |-- config.ru
                      |-- Gemfile
                      |-- Gemfile.lock
                      |-- README.md
                      |-- Rakefile
```
####Dockerfile
```Dockerfile
# 19.01.20現在最新安定版
FROM ruby:2.5.3

# railsコンソール中で日本語入力するための設定 <- NEW
ENV LANG C.UTF-8

# RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs
# /var/lib/apt/lists配下のキャッシュを削除し容量を小さくする <- NEW
RUN apt-get update -qq && \
    apt-get install -y build-essential \
                       libpq-dev \
                       nodejs \
    && rm -rf /var/lib/apt/lists/*

# 作業ディレクトリの設定
RUN mkdir /app_name
ENV APP_ROOT /app_name
WORKDIR $APP_ROOT

# gemfileを追加する
ADD ./src/Gemfile $APP_ROOT/Gemfile
ADD ./src/Gemfile.lock $APP_ROOT/Gemfile.lock

# gemfileのinstall
RUN bundle install
ADD ./src/ $APP_ROOT
```
####docker-compose.yml
```docker-compose.yml
version: '3'
services:
  db:
    image: mysql:5.7
    volumes:
      - ./src/db/mysql_data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: root
    ports:
      - "3306:3306"

  web:
    build: .
    command: rails s -p 3000 -b '0.0.0.0'
    volumes:
      - ./src:/app_name
    ports:
      - "3000:3000"
    links:
      - db
```
####何もない状態からrailsサーバーとMySQLのコンテナを立ててくれるシェル(2019-02-04追記)
[ファイル構成](https://qiita.com/azul915/items/5b7063cbc80192343fc0#%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E6%A7%8B%E9%80%A0%E3%83%9B%E3%82%B9%E3%83%88%E5%81%B4)
###### 1. 任意の場所にアプリを作って行きたいフォルダ（作業ディレクトリ）を作成し、作ったフォルダ配下に移動

```bash
$ mkdir sampleApp
$ cd sampleApp
```
###### 2. 以下のdocker-rails.shシェルをコピー、sampleAppフォルダ配下に置く

###### 3. sampleAppフォルダ配下でシェルのファイルを実行

```bash
$ bash docker-rails.sh 
```

```bash:docker-rails.sh
#!/bin/bash

echo "docker pull ruby:2.5.3"
docker pull ruby:2.5.3

echo "docker pull mysql:5.7"
docker pull mysql:5.7

echo "docker images"
docker images

# make Dockerfile
cat <<'EOF' > Dockerfile
FROM ruby:2.5.3
ENV LANG C.UTF-8
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs && rm -rf /var/lib/apt/lists/*
RUN gem install bundler
WORKDIR /tmp
COPY src/Gemfile Gemfile
COPY src/Gemfile.lock Gemfile.lock
RUN bundle install
ENV APP_HOME /app_name
RUN mkdir -p $APP_HOME
WORKDIR $APP_HOME
COPY . $APP_HOME
EOF


# make 'src' directory and cd 'src' directory
mkdir src && cd src


# make Gemfile
cat <<'EOF' > Gemfile
source 'https://rubygems.org'
gem 'rails'
EOF

# make Gemfile.lock()
touch Gemfile.lock


# cd ../
cd ../


# make docker-compose.yml
cat <<'EOF' > docker-compose.yml
version: '3'
services:
  db:
    image: mysql:5.7
    volumes:
      - ./src/db/mysql_data:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=root
      - MYSQL_DATABASE=root
    ports:
      - "3306:3306"
  web:
    build: .
    command: rails s -p 3000 -b '0.0.0.0'
    volumes:
      - ./src:/app_name
    ports:
      - "3000:3000"
    links:
      - db
    tty: true
    stdin_open: true
EOF

echo "docker-compose run web rails new . --force --database=mysql --skip-bundle"
docker-compose run web rails new . --force --database=mysql --skip-bundle

# cd 'src'
cd src

# fix Gemfile
echo "fix Gemfile(uncoment mini_racer)"
cat Gemfile | sed "s/# gem 'mini_racer'/gem 'mini_racer'/" > __tmpfile__
cat __tmpfile__ > Gemfile
rm __tmpfile__


# cd ../
cd ../


docker-compose build

# cd src/
cd src/


# fix config/database.yml
echo "fix config/database.yml"
cat config/database.yml | sed 's/password:$/password: root/' | sed 's/host: localhost/host: db/' > __tmpfile__
cat __tmpfile__ > config/database.yml
rm __tmpfile__

# cd ../
cd ../


echo "docker-compose run web rake db:create"
docker-compose run web rake db:create

echo "docker-compose up"
docker-compose up
```