DockerでRails環境を構築する

最近副業でRailsを始めたので勉強がてらRailsやろうとなったので、Dockerで環境構築してみました。

[作ったものはこちら](https://github.com/natsume0718/DockerForRails)

## 構成

下記構成で作成します。

- Ruby
- Nginx
- MySQL

ディレクトリ構造はこんな感じです。

![スクリーンショット 2021-05-08 18.35.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/174757/7099e9be-bc39-f4d9-2ec6-35ff037e6151.png)


## Railsの動作環境の作成

### Rails用のDockerfileの作成
まずは`Dockerfile`を作成します。
`src`ディレクトリを作成し、中に`Dockerfile`を作成。

内容は下記の様にします。

```dockerfile:src/Dockerfile
# マルチステージビルドでNode入れる
FROM node:14-alpine as node

FROM ruby:2.7

# nodeとyarnコピー
COPY --from=node /opt/yarn-* /opt/yarn
# シンボリックリンクはる
RUN ln -fs /opt/yarn/bin/yarn /usr/local/bin/yarn

RUN apt-get update && \
    apt-get install -y git vim less build-essential graphviz cron bash g++ nodejs && \
    apt-get clean

#Railsアプリのルートディレクトリ作成
ENV RAILS_ROOT /var/www/src
RUN mkdir -p $RAILS_ROOT

WORKDIR $RAILS_ROOT

RUN mkdir -p $RAILS_ROOT/tmp/sockets && mkdir -p $RAILS_ROOT/tmp/pids

RUN node -v && yarn -v

# ホストのGemfileのコピー
COPY Gemfile Gemfile
COPY Gemfile.lock Gemfile.lock

RUN bundle install --jobs 20 --retry 5
```

解説はコメントにある通りですが、ベースイメージに`ruby:2.7`を使用します。
なにが使えるかや、どんなイメージが有るかはDockerHubを見ます。

Railsは使用するのにNode.jsが必要です。
そのため、マルチステージビルドというのを利用します。

簡単に言うと、フォルダ内部のファイル移動と似たようなイメージです。
`FROM node:14-alpine as node`でnodeの環境ビルドされるので、そこからnodeを使うためのものを全部移動しましょう的なことです。

あとは、Railsで使うもの入れてます。

`/tmp/sockets`はUnixソケットでnginxと通信したいので配置用のフォルダです。

`RUN node -v && yarn -v`は仮にnodeやyarnがなかったときにビルドの途中で落としたいのでいれてます。

### Gemfileの作成

`src`内に`Gemfile`と`Gemfile.lock`を作成します。仮に既存のプロジェクトの場合はここに配置します。
`Gemfile`は下記の様にしておきます。`Gemfile.lock`は空で大丈夫です。

```
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby '2.7.3'

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 6.1.0'

```

## docker-compseを作成しnginxとMySQLと連携する

作業ディレクトリ(`src`の1つ上)内に`docker-compose.yml`を作成します。
内容はこんな感じです。

```yml:docker-compose.yml
version: '3'

networks:
  rails:

volumes:
  db-volume:
  bundle-data:
  tmp-data:

services:
  nginx:
    image: nginx:stable-alpine
    container_name: nginx
    ports:
      - ${APP_PORT:-80}:80
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
      - ./nginx/logs:/var/log/nginx
      - tmp-data:/var/www/src/tmp
    depends_on:
      - rails
    networks:
      - rails

  rails_db:
    image: mysql:8.0
    container_name: rails_db
    command: --default-authentication-plugin=mysql_native_password
    ports:
      - ${DB_PORT:-3360}:3306
    volumes:
      - db-volume:/var/lib/mysql
    environment:
      MYSQL_DATABASE: ${DB_DATABASE}
      MYSQL_USER: ${DB_USERNAME}
      MYSQL_PASSWORD: ${DB_PASSWORD}
      MYSQL_RANDOM_ROOT_PASSWORD: 'yes'
    networks:
      - rails

  rails:
    container_name: rails
    build:
      context: ./src
      dockerfile: Dockerfile
    depends_on:
      - rails_db
    volumes:
      - .:/var/www
      - bundle-data:/usr/local/bundle
      #ソケット通信用ファイルをnginxコンテナと共有
      - tmp-data:/var/www/src/tmp
    command: puma -b unix:/var/www/src/tmp/sockets/puma.sock
    environment:
      TZ: 'Asia/Tokyo'
      DB_HOST: rails_db
      DB_DATABASE: ${DB_DATABASE}
      DB_USERNAME: ${DB_USERNAME}
      DB_PASSWORD: ${DB_PASSWORD}
    networks:
      - rails

```

まずはnginxから
nginxのイメージをそのまま利用します。
設定ファイルをバインドマウントしてます。UnixソケットはRailsコンテナのが必要なので、名前付きボリュームで共有しています。
その他エラー時に対応したいのでlogもバインドしてます。
設定ファイルは後述します。

次にMySQLです。
MySQLのイメージをそのまま利用します。
`command`で認証方式を変更しています。
あとは環境変数の設定しています。

次にRailsです。
`build`に先程作成したDockerfileを指定します。

`tmp-data`には`puma`のソケット場所をしています。
そして、`command`でUnixソケットで接続するように指定します。
railsの`puma.rb`を指定してもいいのですが、記載不要で対応したいので`command`で行います。

### 環境変数
環境変数を設定していきます。
`${DB_PASSWORD}`のようになってる箇所ですね。

作業ディレクトリ内に`.env`を作成します。
DBの接続情報を記載します。`DB_HOST`はrailsにわたすようにMySQLのコンテナ名を記載します。

```text:.env
DB_PORT=3360
DB_DATABASE=rails
DB_USERNAME=homestead
DB_PASSWORD=secret
DB_HOST=rails_db
```

### nginx設定

作業ディレクトリに`nginx`ディレクトを作成します。
`default.conf`を作成します。

`default.conf`の内容は下記です。

```config:default.conf

upstream rails_app {
  # ソケット通信したいのでpuma.sockを指定
  server unix:/var/www/src/tmp/sockets/puma.sock;
}

server {
  listen 80;
  # ドメインもしくはIPを指定
  server_name localhost;

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

  # ドキュメントルートの指定
  root /var/www/src/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 @rails;
  keepalive_timeout 5;

  location ~ /\. {
    deny all;
  }

  location ~* ^.+\.(rb|log)$ {
    deny all;
  }

  # リバースプロキシ関連の設定
  location @rails {
    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://rails_app;
  }
}
```

ソケット通信の設定とログの出力先を設定しています。

## RailsでMySQLの接続情報

`src/config/database.yml`からDBの接続情報を変更します。

```yml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  database: <%= ENV.fetch('DB_DATABASE', 'rails') %>
  username: <%= ENV.fetch('DB_USERNAME', 'homestead') %>
  password: <%= ENV.fetch('DB_PASSWORD', 'secret') %>
  host: <%= ENV.fetch('DB_HOST', 'rails_db') %>
```

## 起動する

あとは起動しましょう！
Railsの新規プロジェクトの場合は下記の様にプロジェクトを作成しましょう。
オプションは自分がイラないもの省いているのと、データベースをMySQLで作成してます。

```bash
docker-compose run --rm rails rails new . --force --database=mysql --skip-test --skip-turbolinks
```

プロジェクトができたら、`docker-compose up -d --build`でコンテナを全部立ち上げます。
`http:localhost`でアクセスできたらOK!

502が出たらnginxのエラーログを見てみましょう