【メモ】Rails+MySQLの環境構築をDockerでおこなう

## 前提

### ローカル環境

- macOS Mojave10.14.6
- Docker for mac 2.2.0.5

### バージョン

- Ruby...~~2.6.3~~ 2.6.6
- Rails...~~5.2.4.1~~ 5.2.8.1
- ~~alpine~~ Debian

#### バージョン変更について

##### Rails

初投稿時には問題なかったが、久々に本記事どおりに構築を進めた際、`rails new ` した時に `mimemagic install error: "Could not find MIME type database in the following locations..."`と出たので変更

[mimemagic問題についての情報を集めていく場所](https://zenn.dev/catnose99/scraps/e4e6042fbbfd60)

##### Ruby

Herokuへのデプロイ時に `heroku The Ruby version you are trying to install does not exist on this stack.` と出たのでその解決策。Rubyのバージョンを `2.6.6` か、 `2.7.2` か、 `3.0.0` に変更すると解決する。

[【解決】heroku-20 You are trying to install ruby-2.6.5 on heroku-20. The Ruby version you are trying to install does not exist on this stack.](https://qiita.com/___fff_/items/12c8ab953bc6aaeb8c24)

### その他

## プロジェクト用ディレクトリ、ファイルを用意

```
$ mkdir myapp
$ cd myapp
$ mkdir -p mnt/mysql \
  docker/mysql/{init,conf.d} \
  docker/ruby \
  src
$ touch .env.example .gitignore docker-compose.yml \
  mnt/mysql/.gitignore \
  docker/mysql/init/init.sql \
  docker/mysql/conf.d/my.cnf \
  docker/ruby/Dockerfile \
  src/{Gemfile,Gemfile.lock}
```

## 各ファイルを編集

### .env.example

```
DB_NAME=rails_tutorial
DB_TEST_NAME=rails_tutorial_test
DB_USER=admin
DB_PASS=admin
DB_ROOT_PASS=password
DB_PORT=23306
TZ=Asia/Tokyo
APP_PORT=13000
```

```bash:
$ cp .env.example .env
```

### .gitignore

```.gitignore:.gitignore
.env
```

```.gitignore:mnt/mysql/.gitignore
*
!.gitignore
```

### my.cnf

```conf:docker/mysql/conf.d/my.cnf
# MySQLサーバーへの設定
[mysqld]
# 文字コード/照合順序の設定
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci

# タイムゾーンの設定
#default-time-zone=SYSTEM

# 以下はMYSQLのみなので
#log_timestamps=SYSTEM

# エラーログの設定
log-error = /var/log/mysql/error.log

# ログ関連
# スロークエリログの設定
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 5
log_queries_not_using_indexes = 0

# 実行ログの設定
#general_log = 1
#general_log_file = /var/log/mysql/query.log

#max_allowed_packet = 32M

# windows tool box 対策
#innodb_use_native_aio=0


wait_timeout = 28800
interactive_timeout = 28800
max_allowed_packet = 100M

# mysqlオプションの設定
[mysql]
# 文字コードの設定
default-character-set = utf8mb4


# mysqlクライアントツールの設定
[client]
default-character-set = utf8mb4
```

### init.sql

```sql:docker/mysql/init/init.sql
-- テスト時にDBを分けたいので、初期立ち上げ時にテスト用DBを作成　
CREATE DATABASE IF NOT EXISTS `rails_tutorial_test`;
GRANT ALL ON rails_tutorial_test.* TO 'admin'@'%';
```

### docker-compose.yml

```yml:docker-compose.yml
version: "3"
services:
  app:
    build:
      context: .
      dockerfile: ./docker/ruby/Dockerfile
    volumes:
      - ./src:/var/www/src
    command: rails s -p 8000 -b '0.0.0.0'
    ports:
      - ${APP_PORT:-13000}:8000
    links:
      - db

  db:
    image: mysql:5.7
    environment:
      - MYSQL_DATABASE=${DB_NAME:-rails_tutorial}
      - MYSQL_USER=${DB_USER:-admin}
      - MYSQL_PASSWORD=${DB_PASS:-admin}
      - MYSQL_ROOT_PASSWORD=${DB_ROOT_PASS:-password}
      - MYSQL_ALLOW_EMPTY_PASSWORD="yes"
      - TZ=${TZ:-Asia/Tokyo}
    ports:
      - ${DB_PORT:-23306}:3306
    volumes:
      - ./mnt/mysql:/var/lib/mysql
      - ./docker/mysql/conf.d:/etc/mysql/conf.d # my.cnfを共有
      - ./docker/mysql/init:/docker-entrypoint-initdb.d # 立ち上げ時にテスト用DBを作成するため
```

### Dockerfile

```Dockerfile:Dockerfile
FROM ruby:2.6.6

RUN apt-get update && apt-get install -y git \
  vim \
  less \
  build-essential \
  libpq-dev \
  libxml2-dev \
  libc-dev \
  make \
  gcc \
  g++ \
  nodejs && \
  apt-get clean && \
  rm -rf /var/lib/apt/lists/*

ENV APP_ROOT /var/www/src

WORKDIR $APP_ROOT

COPY ./src/Gemfile $APP_ROOT/Gemfile
COPY ./src/Gemfile.lock $APP_ROOT/Gemfile.lock

RUN bundle install && \
    apt-get update

COPY ./src/ $APP_ROOT
RUN rm -rf /usr/local/bundle/cache/* ${APP_ROOT}/vendor/bundle/cache/*
```

### Gemfile

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


## アプリケーション作成

```bash:
$ docker compose run app rails new . --force --no-deps --database=mysql --skip-bundle
```

- ディレクトリが下記のようにできていれば成功

```bash:
$ tree -L 1 .
.
├── Dockerfile
├── Gemfile
├── Gemfile.lock
├── README.md
├── Rakefile
├── app
├── bin
├── config
├── config.ru
├── db
├── docker-compose.yml
├── lib
├── log
├── package.json
├── public
├── storage
├── test
├── tmp
└── vendor
```

## config/database.yml を編集

`config/database.yml`がなければ作成

```bash:
$ vi config/database.yml
```

```yml:config/database.yml
# ...

default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: admin  # docker-compose.ymlの設定と合わせる
  password: admin  # docker-compose.ymlの設定と合わせる
  host: db         # docker-compose.ymlのサービス名と合わせる

development:
  <<: *default
  database: rails_tutorial  # docker-compose.ymlの設定と合わせる
  host: db                  # docker-compose.ymlの設定と合わせる

# ...

test:
  <<: *default
  database: rails_tutorial_test  # docker-compose.ymlの設定と合わせる
  host: db                  # docker-compose.ymlの設定と合わせる

# ...

production:
  <<: *default
  database: rails_tutorial
  username: admin   # docker-compose.ymlの設定と合わせる
  password: <%= ENV['MYAPP_DATABASE_PASSWORD'] %>
```

## docker-compose

```bash:
$ docker compose build
$ docker compose up -d
$ docker compose exec app bash
$ rails -v  # バージョンが表示されれば成功
$ rails db:create
Database 'rails_tutorial' already exists
Database 'rails_tutorial_test' already exists
```

## 画面確認

- `localhost:13000` にアクセスし、TOP 画面が表示されれば成功
![initial_screen.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553584/facf04a0-9f5a-793f-0fae-ff9d2ef27690.png)

## Herokuにデプロイ

### Heroku Appの作成 ~ Gitセットアップ

```bash:
$ cd src
$ heroku --version
# heroku/7.60.2 darwin-x64 node-v14.19.0

$ heroku login
$ heroku apps:create kabatech255railstutorial
# Creating ⬢ [アプリ名]... done
# https://[アプリ名].herokuapp.com/ | https://git.heroku.com/[アプリ名].git
# ...

$ git remote -v
heroku  https://git.heroku.com/[アプリ名].git (fetch)
heroku  https://git.heroku.com/[アプリ名].git (push)
$ git init
$ git add
$ git commit -m "first commit"
$ git branch -M main
# まだプッシュしない
```

### MySQLを使用できるように設定

Herokuはデフォルトで **PostgreSQL** を採用しているので、MySQLが使えるよう設定する。

```bash:
# cleardb: クラウド上でMySQLを使えるサービス
# ignite: プラン名。igniteは無料
$ heroku addons:create cleardb:ignite -a kabatech255railstutorial
$ heroku config
CLEARDB_DATABASE_URL: mysql://[ユーザー名]:[パスワード]@[ホスト名]/[データベース名]?reconnect=true

# 上記を参考にherokuの環境設定を追加
$ heroku config:add DB_NAME='[データベース名]' -a kabatech255railstutorial
$ heroku config:add DB_USERNAME='[ユーザー名]' -a kabatech255railstutorial
$ heroku config:add DB_PASSWORD='[パスワード]' -a kabatech255railstutorial
$ heroku config:add DB_HOSTNAME='[ホスト名]' -a kabatech255railstutorial
$ heroku config:add DB_PORT='3306' -a kabatech255railstutorial
$ heroku config:add DATABASE_URL='mysql2://[ユーザー名]:[パスワード]@[ホスト名]/[データベース名]?reconnect=true' -a kabatech255railstutorial

$ heroku config heroku config -a kabatech255railstutorial
=== [アプリ名] Config Vars
CLEARDB_DATABASE_URL:     mysql://[ユーザー名]:[パスワード]@[ホスト名]/[データベース名]?reconnect=true
DATABASE_URL:             mysql2://[ユーザー名]:[パスワード]@[ホスト名]/[データベース名]?reconnect=true
DB_HOSTNAME:              [ホスト名]
DB_NAME:                  [データベース名]
DB_PASSWORD:              [パスワード]
DB_PORT:                  3306
DB_USERNAME:              [ユーザー名]
# ...
```

#### 注意

`heroku config:add DATABASE_URL=`のところで、`CLEARDB_DATABASE_URL`をコピペして、mysql://~~~でセットすると、あとでエラーが出る。

`mysql://`のところを`mysql2://`に修正してセットすること

[【Heroku】デプロイ後エラー対処 Could not load the 'mysql' Active Record adapter.](https://qiita.com/tsuboyataiki/items/47cbdae2f87cd5ca9ffe)

### 本番用にファイルを編集

#### routes.rb

この設定をしないとHerokuへデプロイした時に `The page you were looking for doesn't exist.` とエラーが出る

[Herokuへデプロイした時に「The page you were looking for doesn't exist. 」エラーが出る](https://qiita.com/tukinasikotonoha/items/1c487f08365ef864490b)

```rb:config/routes.rb
Rails.application.routes.draw do
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
  root 'application#hello'
end
```

#### application_controller.rb

```rb:app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  
  def hello
    render html: "hello, world!"
  end
end
```

#### database.yml

```yml:config/database.yml
# ...
production:
  <<: *default
  database: heroku.configのDB_NAME
  username: heroku.configのDB_USERNAME
  password: heroku.configのDB_PASSWORD
  host: heroku.configのDATABASE_URL
  # password: <%= ENV['SRC_DATABASE_PASSWORD'] %>
```

### ソースのプッシュ

```bash:
$ git push heroku main
$ heroku rake db:migrate
$ heroku open
```

`https://kabatech255railstutorial.herokuapp.com/` にアクセスし、`hello., world!` と文字列が出ていたらOK。

### 参考

- [HerokuでのアプリのデプロイでMySQLを使用してみた。](https://qiita.com/Takao_/items/aeb3570b42a6aeb5461f)
- [コマンドでherokuに無料でMysqlをたてる](https://qiita.com/chatrate/items/b2703cb7854259bf343e)
- [Herokuへデプロイした時に「The page you were looking for doesn't exist. 」エラーが出る](https://qiita.com/tukinasikotonoha/items/1c487f08365ef864490b)
- [【Heroku】デプロイ後エラー対処 Could not load the 'mysql' Active Record adapter.](https://qiita.com/tsuboyataiki/items/47cbdae2f87cd5ca9ffe)