OREMATOPEE

プログラミング、気になったこと、メモ書き...etc

なるべく最新Verで構築するRails6開発環境(Docker + Rails + Nginx + Puma + MySQL)

はじめに

こんにちは、Web系エンジニア転職にむけて学習中の Npakk と申します。

Railsを学習するにあたって開発環境を構築したので、その手順を少し解説を交えながらご紹介します。
Dockerでの構築経験はあまりなく、経験も乏しいのであくまで参考程度にご覧ください。
もし間違いやご指摘などあれば、ぜひぜひお願いいたします!

Dockerを使用して、ローカル環境でRailsのWelcomeページを確認できるまでが、この記事のゴールです。

参考記事

対象読者

  • Railsを学びたいけど、環境をどう作ればいいかわからない初学者の方
  • なるべく新しいRails環境を作りたい方

前提

  • Macユーザーを対象としています
  • Docker for Macがインストールされているものとします
  • Nginx と Puma を連携させています
  • 最新バージョンは、記事執筆時点で最新という意味です
  • 全てのソフトウェアが最新バージョンなわけではありません
  • 最新版だから動作が安定していたり、速度が速いというわけではありません

バージョン

  • ホストOS(macOS Catalina 10.15.5)
  • Docker(19.03.8)
  • docker-compose(1.25.5)
  • Ruby(2.7.1)
  • Ruby on Rails(6.0.3.2)
  • Nginx(1.19.0)
  • MySQL(8.0)

1. ディレクトリの作成

ディレクトリ構成としては以下のようになります。
あくまで、手動で作成する項目のみ記載しています。

/webapp
├── Dockerfile
├── Gemfile
├── Gemfile.lock
├── containers
│   └── nginx
│       ├── Dockerfile
│       └── nginx.conf
├── docker-compose.yml
├── entrypoint.sh
└── environments
    └── db.env

1-1. アプリケーションルート

どこでもいいですが、わかりやすいところがおすすめです。

$ mkdir webapp

1-2. Nginxコンテナ用ディレクト

Nginxは、Rails・DBとは別のディレクトリを作ります。
設定ファイルや DockerFile を別途配置します。

$ mkdir -p webapp/containers/nginx

1-3. 環境変数ディレクト

DBで使うユーザーのパスワードなどを記載したファイルを配置します。

$ mkdir webapp/environments

2. コンテナ生成用のファイルを作成

以降はアプリケーションルート内での操作となります。

$ cd webapp

2-1. Rails用Dockerfile

Ruby と Node.js、yarnのバージョン指定は後述する docker-compose.yml から引数として指定します。

Rails6からは標準で Webpacker というgemを使用しており、yarn というパッケージ管理ソフトに依存しています。
yarn がないとうまく動かないため、これをコンテナにインストールする必要があります。

また entrypoint.sh についてですが、Dockerをコマンドで停止せずに強制終了してしまうと、Railsサーバーが開かれたままになるため、次からコンテナを起動するときにエラーが発生します。
その問題を回避するために server.pid を削除しています。

$ vim Dockerfile
ARG RUBY_VERSION
FROM ruby:$RUBY_VERSION

ARG NODE_MAJOR
ARG YARN_VERSION

# ログインシェルとしてbashを使用する
SHELL ["/bin/bash", "-c"]

# nodejs取得
RUN curl -sL https://deb.nodesource.com/setup_$NODE_MAJOR.x | bash -

# 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

# リポジトリを更新し依存モジュールをインストール
RUN apt-get update -qq &&\
  apt-get install -y\
  build-essential\
  nodejs\
  yarn=$YARN_VERSION-1

# ルート直下にwebappという名前で作業ディレクトリを作成(コンテナ内のアプリケーションディレクトリ)
RUN mkdir /webapp
WORKDIR /webapp

# ホストのGemfileとGemfile.lockをコンテナにコピー
ADD Gemfile /webapp/Gemfile
ADD Gemfile.lock /webapp/Gemfile.lock

# bundle installの実行
RUN bundle install

# ホストのアプリケーションディレクトリ内をすべてコンテナにコピー
ADD . /webapp

# puma.sockを配置するディレクトリを作成
RUN mkdir -p tmp/sockets

# コンテナ開始時に必ず実行されるシェルスクリプトをコンテナにコピー
ADD entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]

2-2. Gemfile

Railsの最新バージョンをこのファイルで指定します。

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

gem 'rails', '6.0.3.2'

2-3. Gemfile.lock

このファイルは作るだけで、中身は空で大丈夫です。

$ touch Gemfile.lock

2-4. Nginx用Dockerfile

$ vim containers/nginx/Dockerfile
FROM nginx:1.19.0

# インクルード用のディレクトリ内を削除
RUN rm -f /etc/nginx/conf.d/*

# Nginxの設定ファイルをコンテナにコピー
ADD nginx.conf /etc/nginx/conf.d/webapp.conf

# ビルド完了後にNginxを起動
CMD /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf

2-5. Nginx用設定ファイル

$ vim containers/nginx/nginx.conf
# プロキシ先の指定
# Nginxが受け取ったリクエストをバックエンドのpumaに送信
upstream webapp {
  # ソケット通信したいのでpuma.sockを指定
  server unix:///webapp/tmp/sockets/puma.sock;
}

server {
  listen 80;
  # ドメインもしくはIPを指定
  server_name example.com [or 192.168.xx.xx [or localhost]];

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

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

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

2-6. DB接続用設定ファイル

ユーザー名・パスワードなどは適宜変更してください。

$ vim environments/db.env
MYSQL_ROOT_PASSWORD=db_root_password
MYSQL_USER=user_name
MYSQL_PASSWORD=user_password

2-7. entrypoint.sh

#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"

2-8. docker-compose.yml

argsにRubyなどのバージョンを指定します。
ここで指定することによりDockerfileに値が渡されます。

$ vim docker-compose.yml
version: '3.7'

services:
  app:
    build:
      context: .
      args:
        RUBY_VERSION: '2.7.1'
        NODE_MAJOR: '14'
        YARN_VERSION: '1.22.4'
    env_file:
      - ./environments/db.env
    command: bundle exec puma -C config/puma.rb
    volumes:
      - .:/webapp
      - public-data:/webapp/public
      - tmp-data:/webapp/tmp
      - log-data:/webapp/log
    depends_on:
      - db
  db:
    image: mysql:8.0
    env_file:
      - ./environments/db.env
    volumes:
      - db-data:/var/lib/mysql
  web:
    build:
      context: containers/nginx
    volumes:
      - public-data:/webapp/public
      - tmp-data:/webapp/tmp
    ports:
      - 80:80
    depends_on:
      - app
volumes:
  public-data:
  tmp-data:
  log-data:
  db-data:

3. Railsアプリケーションの生成と編集

3-1. Railsアプリケーションの生成

ここまでファイルとディレクトリを準備できたら、Railsアプリケーションを作成します。

以下のコマンドを実行すると、コンテナ内でRailsアプリケーションが生成されます。
DBにはMySQLを指定し、gemをこの時点でインストールしないようにbundle installの実行を抑制しています。

$ docker-compose run --rm app rails new . --force --database=mysql --skip-bundle

コンテナ内で生成されたアプリケーションをホスト側から編集するには、いちいちコンテナを実行しないといけないため不便です。

そこで、コンテナ内に生成されたディレクトリと、ホスト側のアプリケーションルートを繋ぎます。
こうすることによって、ホスト側のアプリケーションルートにファイルが生成されます。
これらのファイルを編集すれば、コンテナ内のディレクトリにも反映されるようになります。
(既にこの対応は以下の箇所で行っているので、安心してください。)

#省略
    volumes:
      - .:/webapp
#省略

実行時に発生するエラー・警告について

Railsアプリケーション生成コマンド実行時、いくつかエラーと警告が発生します。
「失敗した!」と思われる前に、以下に記載するものについては無視してください。
(記載した以外のエラーや警告がもし発生した場合、一度最後まで手順を実行することをおすすめします。)

apt-key
deb.nodesource.comから落としてきたシェルスクリプトに記載されたapt-keyコマンドで発生した警告です。
詳細を調べてもいまいちよくわからなかったのですが、これを無視してもRailsの環境は構築できます。
(誰か詳しい方がいたら教えてください…)

Step 6/18 : RUN curl -sL https://deb.nodesource.com/setup_$NODE_MAJOR.x | bash -
・
・
・
+ curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -
Warning: apt-key output should not be parsed (stdout is not a terminal)

debconf
これもいまいちよくわかりません。(書いといてなんだって話なんですが。)
apt-utilsをインストールしても消えなかったです。
無視しても大丈夫です。

Step 8/18 : RUN apt-get update -qq &&  apt-get install -y  build-essential  nodejs  yarn=$YARN_VERSION-1
・
・
・
debconf: delaying package configuration, since apt-utils is not installed

mysql2
Railsアプリケーション生成コマンドを実行して、最後に出力されたのがこのエラーです。
Gemfileにリストされたmysql2のgemがないよってことで、bundle installを迫ってきています。

このコマンドを実行した時点では、GemfileにはRailsしか記載されておらず、推測ですが、下記の流れで処理されているのではないかと思います。

  1. Gemfileに記載されたRalilsがインストールされる
  2. Rails newにより、Gemfileの記載が更新されmysql2などが追加される
  3. 最後に、Gemfileに書かれたRails以外のgemがインストールされていないためエラーが発生

このエラーを無視してもWelcomeページは確認できるので、一度最後まで手順を実施してみてください。

Could not find gem 'mysql2 (>= 0.4.4)' in any of the gem sources listed in your Gemfile.
Run `bundle install` to install missing gems.

3-2. 権限変更

生成したRailsアプリケーションのファイル群の所有権が root となっているので、現在のログインユーザーに変更します。

$ sudo chown -R $USER:$USER .

3-3. puma.rbの編集

$ vim config/puma.rb

元の記載は削除して、以下の内容を貼り付けてください。

threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i
threads threads_count, threads_count
port        ENV.fetch("PORT") { 3000 }
environment ENV.fetch("RAILS_ENV") { "development" }
plugin :tmp_restart

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

3-4. database.ymlの編集

$ vim 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: webapp_development

test:
  <<: *default
  database: webapp_test

上記のMYSQL_USER と MYSQL_PASSWORD は DB接続用の情報ファイル で定義した環境変数名を設定します。

4. イメージのビルドとコンテナの起動

いよいよコンテナを起動します!

4-1. イメージのビルド

Dockerfile 及び、DockerHub より引っ張ってきたイメージを全てビルドします。

$ docker-compose build

ビルドが完了したら以下のコマンドで確認します。

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
webapp_web          latest              0ae7b3fc51fd        38 seconds ago      132MB
webapp_app          latest              d661a9898271        47 seconds ago      1.27GB
<none>              <none>              83d4ec18ac0c        6 minutes ago       1.06GB
ruby                2.7.1               9b840f43471e        9 days ago          842MB
nginx               1.19.0              2622e6cca7eb        3 weeks ago         132MB
mysql               8.0                 be0dbf01a0f3        3 weeks ago         541MB

4-2. コンテナの起動

ビルドしたら、下記のコマンドでコンテナを立ち上げます。

$ docker-compose up -d

コンテナが起動しているか確認します。

$ docker-compose ps

全てのコンテナの State が Up となっていることを確認してください。

    Name                  Command               State          Ports
---------------------------------------------------------------------------
webapp_app_1   entrypoint.sh bundle exec  ...   Up
webapp_db_1    docker-entrypoint.sh mysqld      Up      3306/tcp, 33060/tcp
webapp_web_1   /docker-entrypoint.sh /bin ...   Up      0.0.0.0:80->80/tcp

ビルドに失敗した場合

イメージのビルドに失敗したり、コンテナの起動に失敗するとローカルにゴミファイルがたまってしまいます。
一度全てきれいにしたい場合は、コンテナとイメージを全て削除するコマンドを使います。
まずは、コンテナの起動を止めてから実行してください。

コンテナを停止する

$ docker-compose stop

すべてのコンテナを削除する

$ docker rm $(docker ps -q -a)

すべてのイメージを削除する

$ docker rmi $(docker images -q)

5. DB設定

5-1. 権限の付与

DBの操作を一般ユーザーで行うため、実行権限を付与します。

GRANT文を記述したSQLファイルを作成します。
user_name は DB接続用の情報ファイル に設定した MYSQL_USER の値に置き換えてください。

$ vim db/grant_user.sql
GRANT ALL PRIVILEGES ON *.* TO 'user_name'@'%';
FLUSH PRIVILEGES;

dbコンテナに向けてクエリを送ります。
パスワードを求められるので、rootのパスワードを入力してください。

$ docker-compose exec db mysql -u root -p -e"$(cat db/grant_user.sql)"

権限が付与されたか確認します。
パスワードを求められるので、一般ユーザーのパスワードを入力してください。

$ docker-compose exec db mysql -u user_name -p -e"show grants;"

実行結果が横に長くてみづらいかもしれません。
MySQL5系では全ての権限が付与されている場合、「ALL PRIVILEGES」と表示されていたみたいですが、8系ではちゃんと全ての権限名が表示されるため、このような横に長い結果になっているようです。

+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Grants for user_name@%                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, RELOAD, SHUTDOWN, PROCESS, FILE, REFERENCES, INDEX, ALTER, SHOW DATABASES, SUPER, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, REPLICATION SLAVE, REPLICATION CLIENT, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, CREATE USER, EVENT, TRIGGER, CREATE TABLESPACE, CREATE ROLE, DROP ROLE ON *.* TO `user_name`@`%`                                                                                                                        |
| GRANT APPLICATION_PASSWORD_ADMIN,AUDIT_ADMIN,BACKUP_ADMIN,BINLOG_ADMIN,BINLOG_ENCRYPTION_ADMIN,CLONE_ADMIN,CONNECTION_ADMIN,ENCRYPTION_KEY_ADMIN,GROUP_REPLICATION_ADMIN,INNODB_REDO_LOG_ARCHIVE,PERSIST_RO_VARIABLES_ADMIN,REPLICATION_APPLIER,REPLICATION_SLAVE_ADMIN,RESOURCE_GROUP_ADMIN,RESOURCE_GROUP_USER,ROLE_ADMIN,SERVICE_CONNECTION_ADMIN,SESSION_VARIABLES_ADMIN,SET_USER_ID,SHOW_ROUTINE,SYSTEM_USER,SYSTEM_VARIABLES_ADMIN,TABLE_ENCRYPTION_ADMIN,XA_RECOVER_ADMIN ON *.* TO `user_name`@`%` |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

5-2. DBの作成

railsコマンドでDBを作成します。

$ docker-compose exec app rails db:create

6. 確認

お疲れさまでした!
下記のlocalhostをクリックして、Welcomeページが表示されるでしょうか。
RailsRuby のバージョンが指定したものであるか、確認してください。

http://localhost Ruby_on_Rails.jpg

あとがき

うまくWelcomeページが表示されましたでしょうか?

参考記事を見ながら構築していったのですが、Rails6用に書かれていなかったため、かなり苦労しました…。(Rails学ぶことが目的だったので、Rails5でよかったですね…)

Rails6に対応したあとはバージョンの記載を一元管理したり、色々なサイトで書かれていることを網羅して今回の構築ファイルたちができあがりました。
とりあえずはこの環境でRails勉強していきます!

もし間違い・ご指摘などあればぜひお願いします。
今度はRails関連の記事でお会いしましょう!(多分)