開発環境において既存のRailsアプリにDockerを導入する方法(Rails、nginx、mysql)

---
※ DockerでRailsの環境構築をする方法に関して新しい記事を書いたので、下記を参照ください。 (2021/1/21更新)

- <a href="https://qiita.com/Yusuke_Hoirta/items/3a50d066af3bafbb8641">Dockerを用いてRuby on Railsの環境構築をする方法( Docker初学者向け )</a>

---
Dockerに興味があり、作成したポートフォリオに組み込もうと思い、様々な記事を見ながら導入を試みましたが、かなり苦戦を強いられました。
無事導入することができましたので、今回は既存のRailsアプリにDockerを導入する方法を書いていきたいと思います。

# 環境
- Mac OS
- Ruby2.5.7
- Rails5.2.4
- mysql5.7
- nginx1.15.8

# 事前準備
- OS上に Docker と docker-compose をインストールしてください。
以下のコマンドを実行して、それぞれ表示されればインストールできています。

```
$ docker -v
Docker version 19.03.8, build afacb8b

$ docker-compose -v
docker-compose version 1.25.5, build 8a1c60f6
```

# ファイル構成
ローカルのファイル構成は最終的に以下の通りになります。

```
myapp
  |-- app
  |-- bin
  |-- config
  |-- containers
    |-- nginx
      |-- Dockerfile
      |-- nginx.conf
  |-- db
  |-- lib
  |-- log
  |-- public
  |-- storage
  |-- test
  |-- tmp
  |-- vendor
  |-- config.ru
  |-- docker-compose.yml
  |-- Dockerfile
  |-- Gemfile
  |-- Gemfile.lock
  |-- package.json
  |-- Rakefile
  |-- README.md
```
# RubyのDockerfileを作成
myappはご自身のアプリケーション名に変えてください。

```html:Dockerfile
FROM ruby:2.5.7

RUN apt-get update -qq && \
    apt-get install -y build-essential \
                       libpq-dev \
                       nodejs \
                       vim

RUN mkdir /myapp

WORKDIR /myapp

ADD Gemfile /myapp/Gemfile
ADD Gemfile.lock /myapp/Gemfile.lock

RUN gem install bundler
RUN bundle install

ADD . /myapp

RUN mkdir -p tmp/sockets
RUN mkdir -p tmp/pids
```
- From に記述している ruby:2.5.7 はご自身のGemfileに書いてあるrubyのバージョンに合わせてください。
- RUN gem install bundler を記述しないと、bundle install が実行できず、以下のようなエラーが表示され、rubyのコンテナが起動できません。

```
ERROR: Service 'app' failed to build: The command '/bin/sh -c bundle install' returned a non-zero code: 20
```

# NginxのDockerfileを作成
```html:containers/nginx/Dockerfile
FROM nginx:1.15.8

RUN rm -f /etc/nginx/conf.d/*

ADD nginx.conf /etc/nginx/conf.d/myapp.conf

CMD /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf
```

# nginx.confを作成
```html:containers/nginx/nginx.conf
upstream myapp {
  server unix:///myapp/tmp/sockets/puma.sock;
}

server {
  listen 80;
  server_name localhost;

  access_log /var/log/nginx/access.log;
  error_log  /var/log/nginx/error.log;

  root /myapp/public;

  client_max_body_size 100m;
  error_page 404             /404.html;
  error_page 505 502 503 504 /500.html;
  try_files  $uri/index.html $uri @myapp;
  keepalive_timeout 5;

  location @myapp {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_pass http://myapp;
  }
}
```

# docker-compose.ymlの作成
```yml:docker-compose.yml
version: '3'
services:
  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_USER: user
      MYSQL_PASSWORD: 1234
      MYSQL_DATABASE: myapp_db
    volumes:
      - mysql-data:/var/lib/mysql
    ports:
      - "4306:3306"

  app:
    build: .
    command: bundle exec puma -C config/puma.rb
    volumes:
      - .:/myapp
      - public-data:/myapp/public
      - tmp-data:/myapp/tmp
      - log-data:/myapp/log

  web:
    build:
      context: containers/nginx
    volumes:
      - public-data:/myapp/public
      - tmp-data:/myapp/tmp
    ports:
      - 80:80

volumes:
  mysql-data:
  public-data:
  tmp-data:
  log-data:
```

# database.ymlを編集
```yml:config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: <%= ENV.fetch('MYSQL_USER') { 'root' } %>
  password: <%= ENV.fetch('MYSQL_PASSWORD') { 'password' } %>
  host: db

development:
  <<: *default
  database: myapp_development

test:
  <<: *default
  database: myapp_test

production:
  <<: *default
  database: <%= ENV['DB_DATABASE'] %>
  adapter: mysql2
  encoding: utf8mb4
  charset: utf8mb4
  collation: utf8mb4_general_ci
  host: <%= ENV['DB_HOST'] %>
  username: <%= ENV['DB_USERNAME'] %>
  password: <%= ENV['DB_PASSWORD'] %>
```

# puma.rbを編集
puma.rbの最下部に以下の記述を追加

```ruby:config/puma.rb
app_root = File.expand_path("../..", __FILE__)
bind "unix://#{app_root}/tmp/sockets/puma.sock"

stdout_redirect "#{app_root}/log/puma.stdout.log", "#{app_root}/log/puma.stderr.log", true
```

# Gemfileの編集
Gemfileに以下の記述を追加します。

```ruby:Gemfile
gem 'mysql2'
```

# コンテナの起動
あとはコンテナを起動するだけです。
アプリケーションのあるディレクトリに移動し以下のコマンドを実行します。

```
$ docker-compose up -d
```
しばらく経つと、最後に以下のように表示され、コンテナの起動が完了します。

```
Creating myapp_db_1  ... done
Creating myapp_app_1 ... done
Creating myapp_web_1 ... done
```
次のコマンドを実行し、コンテナが起動できているか確認します。
以下のように表示されれば、コンテナの起動は成功しています。

```
$ docker-compose ps
     Name                   Command               State                 Ports              
-------------------------------------------------------------------------------------------
myapp_app_1   bundle exec puma -C config ...   Up                                       
myapp_db_1    docker-entrypoint.sh mysqld      Up      0.0.0.0:4306->3306/tcp, 33060/tcp
myapp_web_1   /bin/sh -c /usr/sbin/nginx ...   Up      0.0.0.0:80->80/tcp               
```

# データベースの作成、マイグレーションファイルの読み込み
最後に以下のコマンドでデータベースの作成とマイグレーションファイルの読み込みをします。

```
$ docker-compose exec app rails db:create db:migrate
```

localhost:80にアクセスして、正しくに表示されればOKです。

# コンテナの停止、削除
以下のコマンドでコンテナの停止、削除ができます。

```
$ docker-compose down
```
再び起動したい場合は先ほどと同様、 "dokcer-compose up -d" でOKです。

以下のコマンドでvolumeも一緒に削除できます。(Mysqlのデータも全て消えるので注意！！)

```
$ docker-compose down -v
```
# Docker導入後の開発
Docker導入後の開発(rails g コマンド や budle install)を行うには Railsのコンテナの中に入る必要があります。
コンテナが起動された状態で、以下のコマンドを実行し、コンテナの中に入ります。

```
$ docker-compose exec app bash
```
ターミナル画面の左側の コンピューター名やユーザー名 が表示されている部分が以下のような感じに変わればコンテナに入ることができています。

```
root@f839434ad777:/myapp#
```
コンテナに入り、以下のコマンドで、Docker導入前の rails s -b 0.0.0.0 で見れていたログを表示することができます。

```
$ tail -f log/development.log
```

以下のコマンドでコンテナから出ることができます。

```
$ exit
```

# 参考記事
- <a href="https://qiita.com/eighty8/items/0288ab9c127ddb683315">Docker + Rails + Puma + Nginx + MySQL</a>
- <a href="https://qiita.com/azul915/items/5b7063cbc80192343fc0">丁寧すぎるDocker-composeによるrails5 + MySQL on Dockerの環境構築(Docker for Mac)</a>