【Rails6/MySQL5】既存アプリにDocker環境を構築した方法（Capistrano・Amazon S3にも対応）

※2022年から技術系の記事は[個人ブログ](https://yamaday0u.net/category/programmer/)に投稿しております。ぜひこちらもご覧ください→[yamaday0u Blog](https://yamaday0u.net/category/programmer/)

Railsで開発した既存アプリにDocker環境を実際に構築した方法をご紹介します。

## ゴール
- 既存のRailsアプリにDockerの開発環境を構築
- Mysqlエラーを起こさずCapistranoによる自動デプロイ

## 開発環境・前提
- Ruby 2.6.5
- Rails 6.0.3.6
- Docker 20.10.6
- docker-compose 1.29.1
- mysql 5.6.50
- Capistrano 3.16.0 
- アプリのインフラにAmazon EC2, S3を使用

## 最初に：Docker環境の構築で一番難しかったこと
ぼくが実際にDocker環境を構築するときに一番難しかったことが、MySQLの接続エラーの解決でした。
これから初めてDocker環境構築をするという方は覚悟して臨んだ方が良いと思います笑

## 各ステップで参考にした資料
順番が前後しますが、先にぼくがDocker環境を構築したときに参考にした資料を紹介します。
このあと紹介するコードも、基本的には以下の参考資料を組み合わせて作りました。

Rails6とMySQLでDocker環境構築
→　[既存のRailsアプリのローカル環境にDockerを導入する方法【Rails6 / MySQL8】（Qiita）](https://qiita.com/takuma_0625/items/2a25dce50ad309993fa3)

Docker導入後の自動デプロイ時のMySQLエラーの解決
→　[DockerコンテナにRailsの環境変数を適用させる方法(aws::Sigv4::Errorsの解決法)（Qiita）]
(https://qiita.com/curry__30/items/f85ab4eaf4ca8164b3b0)
→　[docker導入後のMysql2::Error::ConnectionError (Unknown MySQL server host 'db' (0))（teratail）:
](https://teratail.com/questions/321052)

## 1. Docker環境の構築
### 1-1. Dockerfile

```Dockerfile:Dockerfile
# 使用するイメージとバージョンを指定/Gemfileに記載されているバージョンに合わせる
FROM ruby:2.6.5

# yarnのインストール
# 使われているコマンドは主要なLinuxディストリビューションの1つであるUbuntuというOSのもの（簡単にいうとLinuxの流派の1つ）
# 参考：https://classic.yarnpkg.com/en/docs/install/#debian-stable（yarn公式サイト）
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
    && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list

# APTのパッケージリストを更新 & 各種パッケージをインストール
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs yarn

# コンテナ内での作業ディレクトリを指定
WORKDIR /group_calendar

# ローカルのGemfileとGemgile.lockをコンテナにコピー（左側がローカル、右側がコンテナ）
COPY Gemfile ./Gemfile
COPY Gemfile.lock ./Gemfile.lock

# bundlerをインストール
RUN gem install bundler
# yarnをインストール
RUN yarn install
# 以下は一旦記述しなくてもOK/後で行うdocker-compose buildで
# babel-sourceの5.8.35がないというエラーが出たので、追加したコード
RUN gem install babel-source -v 5.8.35
# bundlerでGemfileからgemをインストール
RUN bundle install

# ローカルのアプリ内の全てのファイルやディレクトリをコンテナの作業ディレクトリないにコピー
COPY . /group_calendar

# entrypoint.shをコンテナの/usr/bin/にコピー
COPY entrypoint.sh /usr/bin/
# ユーザーに関わらず/usr/bin/entrypoint.shに実行権限を付与して
# シェルスクリプトファイルを実行可能
RUN chmod +x /usr/bin/entrypoint.sh
# コンテナー起動時に毎回実行されるスクリプトを追加
ENTRYPOINT ["entrypoint.sh"]

# コンテナの接続先のポートを指定
EXPOSE 3000

# イメージ実行時に起動させる主プロセスを設定
CMD ["rails", "server", "-b", "0.0.0.0"]
```
### 1-2. entrypoint.sh
`Dockerfile`のENTRYPOINTで指定しているスクリプトの内容を記述します。
正直、`entrypoint.sh`については僕自身もよくわかっていないのですが、ひとまず２行目のプロセスIDを削除する記述が特に重要だと認識しています。

```entrypoint.sh
#!/bin/bash
# エラーが発生するとスクリプトを終了する
set -e

# Remove a potentially pre-existing server.pid for Rails.
# Railsのpidが存在している場合に削除する/Rails特有の問題への対処
rm -f /group_calendar/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
# DockerfileのCMDで渡されたコマンド（→Railsのサーバー起動）を実行
exec "$@"
```
### 1-3. docker-compose.yml
`docker-compose.yml`は、公式ではcomposeファイルと呼ばれ、コマンドを1回実行することで、ファイルから設定を読み込んで複数のコンテナから成るサービスを構築し、自動的に全てのコンテナを起動することを可能にします。

```docker-compose.yml
#docker-composeのバージョン
version: '3'
services:
  db:
    # 既存アプリとあわせる/ターミナルで[$ mysql --version]で確認
    image: mysql:5.6.50
    # 環境変数の設定/後述のdatabase.ymlの設定時に使用
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: root
    # ポート番号を指定/[ローカル:コンテナ]
    ports:
      - "4306:3306"
    # 名前付きボリュームでDBのデータを永続化
    # [データボリューム名:コンテナ内のパス]
    volumes:
      - mysql_data:/etc/mysql/conf.d
  web:
    # Dockerfileのパスをカレントディレクトリに指定
    build: .
    # server.pidファイルを削除してからrailsサーバー起動
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    # 環境変数の設定
    environment:
      - MYSQL_HOST=db
      - MYSQL_PASSWORD=password
    # ディレクトリのマウント
    volumes:
      - .:/group_calendar
    ports:
      # ポート番号を指定/[ローカル:コンテナ]
      - 3000:3000
    # サービス間の依存関係を指定
    # この場合、db → webの順でサービスを起動
    depends_on:
      - db
    # コンテナを起動させ続けるための設定
    # make container stay launched
    tty: true
    # stdinはstandard input（標準入力）の略
    # コンテナの外から命令を送るための設定
    stdin_open: true
# 名前付きボリュームを使うときはトップレベルでもvolumesでデータボリューム名を定義する
volumes:
  # db:volumes:で作成したボリューム名を記述
  mysql_data:
```
### 1-4. database.yml
コンテナ内でデータベースを使えるように設定します。
`development`、`test`、`production`全てに共通する設定を記述する`default`の部分に以下の記述をします。

```config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: <%= ENV['MYSQL_PASSWORD'] || '' %> # 記述箇所
  socket: /tmp/mysql.sock
  host: <%= ENV['MYSQL_HOST'] || 'localhost' %> # 記述箇所
```
`password:`と`host:`に、`docker-compose.yml`の`services:web:environment`で設定した環境変数を使用しています。

また` <%= ENV['MYSQL_PASSWORD'] || '' %>`の記述になっているのは、コンテナ環境でも自分のPC上のローカル環境でもデータベースを使用できるようにするための仕掛けです。

もっと分かりやすく言うと、`docker-compose run`と`rails s`のどちらのコマンドで立ち上げた開発環境でもデータベースを使用できるようにしています。

コンテナ環境だとローカル環境と比べて起動に時間がかかってしまうので、気軽に動作確認などをしたいときにも対応できるようにしました。

~~詳しくはまた別の記事にしたいと思います。~~
2021年5月31日追記：記事を投稿しました！
[【Rails6/MySQL5】Docker環境とローカル環境両方でデータベースを使えるようにする方法](https://qiita.com/yamaday0u/items/3989ad733f9131a45d7b)

参考記事：[docker導入後のMysql2::Error::ConnectionError (Unknown MySQL server host 'db' (0))（teratail）:
](https://teratail.com/questions/321052)

## 2. コンテナ型仮想環境でS3を使えるようにする
ここからはAmazon S3を使えるようにするための記述を紹介します。

### 2-1. 問題点
Amazon S3に画像を保存する場合、`storage.yml`に保存先の情報を以下のように記述します。

```storage.yml
amazon:
  service: S3
  region: "リージョン名（例：ap-northeast-1"
  bucket: バケット名
  access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %>
  secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %>
```
ソースコードをただ公開するだけなら、環境変数の`AWS_ACCESS_KEY_ID`と`AWS_SECRET_ACCESS_KEY`の値は自身のPC上で設定しておけばよいです。

しかし今回はDockerを使っているので、他の開発者とも共有することを想定しているので、
環境変数の中身まで他の開発者に共有する必要があります。

ですがセキュリティの問題から、AWSの公開鍵と秘密鍵を平文で記述するわけにはいきません。
かといって自身のPCの環境変数を参照するようにしていると他の開発者では使用できません。

これを解決してくれるのが`dotenv-rails`というgemです。

### 2-2. dotenv-railsの導入
`dotenv-rails`は、Railsのアプリ内で環境変数を管理することを実現してくれるgemです。
[dotenvの公式GitHub](https://github.com/bkeepers/dotenv)
Gemfileの一番下に`dotenv-rails`を追記し、bundle installします。

```:Gemfile
gem 'dotenv-rails'
```

```zsh:ターミナル
bundle install
```

### 2-3. .envの記述
`.env`ファイルを手動で作成し、AWSの公開鍵と秘密鍵を環境変数に代入します。

```:.env
AWS_ACCESS_KEY_ID = "AWSの公開鍵"
AWS_SECRET_ACCESS_KEY = "AWSの秘密鍵"
```
### 2-4. .gitignoreの記述
`.env`の内容がGitHubで公開されては困るので、`.gitignore`に`.env`を追記します。

```:.gitignore
.env
```


## 3. 自動デプロイ（本番環境）でもMySQLを使えるようにする
本番環境への自動デプロイ時にMySQLがで連発して、かなり苦戦したのですが、既に紹介した2-1から2-4までの設定を全て実施したところ、無事にデプロイも完了できました。

各々のアプリの設定によっては当然エラーが起きる可能性がありますが、既存のRailsアプリへのDocker環境の構築の1つの例として皆様の助けになれば幸いです。


## 参考資料

- Docker環境の構築
 - [既存のRailsアプリのローカル環境にDockerを導入する方法【Rails6 / MySQL8】（Qiita）](https://qiita.com/takuma_0625/items/2a25dce50ad309993fa3)
 - [[備忘録] [初心者] Docker Compose / Rails(公式doc.)について自分用補足#1 (Dockerfile, entrypoint.sh)（Qiita）](https://qiita.com/RiSE_blackbird/items/e16c84cc18d4639d5d4f)
 - [Dockerのデータを永続化！Data Volume（データボリューム）の理解から始める環境構築入門（Enjoy IT Life）](https://nishinatoshiharu.com/docker-volume-tutorial/)
- Docker環境でAmazon S3を使用するための環境変数の設定
 - [dotenvの公式GitHub](https://github.com/bkeepers/dotenv)
 - [DockerコンテナにRailsの環境変数を適用させる方法(aws::Sigv4::Errorsの解決法)（Qiita）]
(https://qiita.com/curry__30/items/f85ab4eaf4ca8164b3b0)
- Docker環境、ローカル環境両方でデータベースを使用する設定/Capistranoによる自動デプロイ中のMySQLエラーの解決
 - [docker導入後のMysql2::Error::ConnectionError (Unknown MySQL server host 'db' (0))（teratail）:
](https://teratail.com/questions/321052)