今回はRails 6とMySQL 8を組み合わせたWebアプリケーションのDocker環境を構築する手順について紹介します。
Rails 6からwebpackerが標準でインストールされるようになったり、MySQL 8からユーザー認証の方式が変わったりと環境構築でつまる部分がいろいろとあったため参考になればと思います。
複数人でもスムーズに開発ができるようにするためリモートリポジトリからcloneしてきたらdocker-compose upするだけでアプリケーションが立ち上がるという環境をゴールにします。
各種バージョンは以下の通りです。
- Ruby on Rails: 6.0.3.2
- Ruby: 2.7.1
- MySQL: 8.0.21
実行環境はDocker Desktop for Mac(バージョン 2.3.0.4)を利用しています。
Railsアプリケーションの準備
ディレクトリの作成・移動をします。
今回作成するRailsアプリケーション名はsample_app
とします。
$ mkdir sample_app && cd $_
rubyイメージを利用してローカル環境にGemfileを作成します。
-v
はバインドマウント(ホストとコンテナのディレクトリの同期)のオプションです。
ホストのカレントディレクトリとコンテナのワークディレクトリを同期させることで、コンテナ上で作成されるファイルをホストに配置します。
$ docker run --rm -v `pwd`:/sample_app -w /sample_app ruby:2.7.1 bundle init
作成されたGemfileのgem "rails"
の部分をアンコメントし、railsのバージョンを指定します。
# frozen_string_literal: true
source "https://rubygems.org"
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
- # gem "rails"
+ gem "rails", '~> 6.0.3.2'
rails new
を実行するDocker環境を構築するためDockerfileを作成します。
Rails 6ではアプリケーションを作成する際にrails webpacker:install
も実行されるのでyarnのインストールを忘れずにしましょう。
今回はnpm
を利用してyarn
をインストールします。
FROM ruby:2.7.1
RUN apt-get update -qq && \
apt-get install -y nodejs \
npm && \
npm install -g yarn
# 作業ディレクトリを/sample_appに指定
WORKDIR /sample_app
# ローカルのGemfileをDokcerにコピー
COPY Gemfile* /sample_app/
# /sample_appディレクトリ上でbundle install
RUN bundle install
Dockerfileをビルドして作成されたイメージを利用してコンテナを起動し、コンテナ上でrails new
をします。
$ docker build -t sample_app .
$ docker run --rm -v `pwd`:/sample_app sample_app rails new . –skip-bundle --database=mysql
docker-compose.yml
を作成します。
今回は説明を簡略化するため、rootユーザーでMySQLに接続しています。
一般ユーザーで接続をする場合はMySQLのイメージに対して以下の環境変数を設定する必要があります。
環境変数 | 内容 |
---|---|
MYSQL_USER | ユーザー名 |
MYSQL_PASSWORD | ユーザーパスワード |
データベースの情報はmysql_data
という名前付きボリュームを作成して永続化します。
version: '3'
services:
web: # Ruby on Railsが起動するコンテナ
build: .
ports:
- '3000:3000' # localhostの3000ポートでアクセスできるようにする
volumes:
- .:/sample_app # アプリケーションファイルの同期
depends_on:
- db
command: ["rails", "server", "-b", "0.0.0.0"]
db: # MySQLが起動するコンテナ
image: mysql:8.0.21
volumes:
- mysql_data:/var/lib/mysql # データの永続化
command: --default-authentication-plugin=mysql_native_password # 認証方式を8系以前のものにする。
environment:
MYSQL_ROOT_PASSWORD: 'pass'
MYSQL_DATABASE: 'sample_app_development'
volumes:
mysql_data: # データボリュームの登録
上記のdocker-compose.yml
の補足説明をします。
MySQLのDockerイメージではMYSQL_DATABASE
に設定された名前のデータベースを作成してくれます。
Ruby on Railsのdevelopment環境では[アプリケーション名]_development
というデータベースを利用するため、sample_app_development
をMYSQL_DATABASE
に登録しています。
これでrails db:create
を実行しなくてもdevelopment環境のデータベースを用意できます。
MySQL 8からは認証方式がmysql_native_password
からcaching_sha2_password
に変更されました。
MySQL 8標準のcaching_sha2_password
の認証方式だとデータベースへ接続できず、Railsアプリケーション起動時に以下のようなエラーメッセージが表示されてしまいます。
Mysql2::Error::ConnectionError
Plugin caching_sha2_password could not be loaded: /usr/lib/x86_64-linux-gnu/mariadb19/plugin/caching_sha2_password.so: cannot open shared object file: No such file or directory
そこで、--default-authentication-plugin=mysql_native_password
で以前のmysql_native_password
の認証方式を利用するようにしています。
次にRuby on Railsのデータベース接続設定を行います。
default: &default
adapter: mysql2
encoding: utf8
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
username: root
password: pass # (今回はrootなので)MYSQL_ROOT_PASSWORDと一致させる
host: db # データベースのコンテナ名を設定する
Railsアプリケーションの起動確認
Ruby on Rails 6とMySQL 8を組み合わせたDocker環境ができあがったので起動をしてみます。
$ docker-compose up
localhost:3000
にアクセスして以下の画面が表示されればOKです。
データベースのデータの永続化についても確認をしてみます。
例としてscaffold
でevent
に関する機能を作成します。
$ docker-compose exec web rails g scaffold event title:string
$ docker-compose exec web rails db:migrate
localhost:3000/events/new
にアクセスし、例として『サンプルイベント』というレコードを登録してみます。
コンテナを削除・起動してもデータが残っていればOKです。
$ docker-compose down
$ docker-compose up
$ docker-compose exec web rails c
> Event.last.title
=> "サンプルイベント"
テスト環境用のデータベースの作成
MYSQL_DATABASE
を利用してdevelopment環境のデータベースを作成することでdb:create
を実行することなくRailsアプリケーションを起動できるようにしました。
しかし、テストコードで利用するtest環境のデータベース([アプリケーション名]_test
)が作られていないためこれだけでは開発環境としては不十分です。
MySQLのDockerイメージでは/docker-entrypoint-initdb.d
にスクリプト(.sql
、.sh
、.sql.gz
)を配置しておくとコンテナ起動時に実行してくれるという機能があります。1
この機能を活用してtest環境用のデータベースを作成します。
スクリプトはアルファベット順で実行されるため、スクリプト間に依存関係がある場合はファイル名も意識してつけるようにしておきましょう。
$ mkdir docker-entrypoint-initdb.d && cd $_
$ vim 00_create.sql
-- test環境用のデータベースを作成する
CREATE DATABASE sample_app_test;
なお、一般ユーザーのデータベースアクセス権はMYSQL_DATABASE
に設定したデータベースのみとなっています。
ですので、一般ユーザーでデータベースに接続する場合はデータベースの作成だけでなく、以下のようなアクセス権の付与も実行する必要があります。
-- webappという名前の一般ユーザーを利用する場合
GRANT ALL ON `sample_app_test`.* TO webapp@'%';
作成したスクリプトをコンテナ上で読み取れるようにするためバインドマウントを追加します。
version: '3'
services:
web:
build: .
ports:
- '3000:3000'
volumes:
- .:/sample_app
depends_on:
- db
command: ["rails", "server", "-b", "0.0.0.0"]
db:
image: mysql:8.0.21
volumes:
- mysql_data:/var/lib/mysql
+ - ./docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
command: --default-authentication-plugin=mysql_native_password
environment:
MYSQL_ROOT_PASSWORD: 'pass'
MYSQL_DATABASE: 'sample_app_development'
volumes:
mysql_data:
データベースが自動作成されることを確認
dbコンテナを削除・起動し、自動でデータベースが作られるか確認をしてみます。
[アプリケーション名]_development
と[アプリケーション名]_test
のデータベースが存在していればOKです。
# データベースの永続化情報も削除して0からデータベースを作成する
$ docker-compose down --volumes
$ docker-compose up
$ docker-compose exec db mysql -uroot -ppass -D sample_app_development
mysql> show databases;
+------------------------+
| Database |
+------------------------+
| information_schema |
| mysql |
| performance_schema |
| sample_app_development |
| sample_app_test | ← test用のdbが作成されている
| sys |
+------------------------+
6 rows in set (0.00 sec)
Railsアプリケーションからも正常に接続できます。
$ docker-compose exec web rails c
> ENV['RAILS_ENV']
=> "development
$ docker-compose exec web rails c -e test
> ENV['RAILS_ENV']
=> "test"
『clone → docker-compose up』だけで環境が立ち上がるようにする
DockerでRailsの開発環境を構築する方法としてよく見かけるのが以下のようなパターンです。
# コンテナを立ち上げる
$ docker-compose up
# データベースの作成
$ docker-compose exec web rails db:create
# テーブルのマイグレーション
$ docker-compose exec web rails db:migrate
Railsアプリケーションとデータベースをdocker-composeで連携させ、アプリケーションで利用するデータベースはコンテナ上で構築するという方法です。
上記の方法でも問題はないのですが、この場合だと初回起動時にデータベースとテーブルの作成をコンテナ上で手動実行する手間がかかります。
複数人でDocker環境を共有する時のことを考えると、リモートリポジトリからアプリケーションファイルをcloneしてきたらdocker-compose up
をするだけで環境が立ち上がるのが理想です。
共有メンバーに対して「初回起動時はデータベースの作成をコンテナ上で実施してね」とわざわざ伝えないといけない状況はできれば避けたいです。
ここからはリモートリポジトリからcloneしたらdocker-compose up
するだけでRailsアプリケーションが起動できるようにするための設定を行います。
yarn installをコンテナ起動時に実行する
開発環境ではバインドマウト(ホストとコンテナのディレクトリの同期)を利用したソースコードの同期がよく利用されます。
リモートリポジトリからcloneしてきたアプリケーションファイルはpackage.json
はありますが、node_modules
ディレクトリはありません。
そのため、cloneしたあとdocker-compose up
をするとnode_modules
がない状態のディレクトリがバインドマウントされます。その結果yarn install
の実行を促すエラーが発生し、アプリケーションが起動できません。
========================================
Your Yarn packages are out of date!
Please run `yarn install --check-files` to update.
========================================
To disable this check, please change `check_yarn_integrity`
to `false` in your webpacker config file (config/webpacker.yml).
yarn check v1.22.5
info Visit https://yarnpkg.com/en/docs/cli/check for documentation about this command.
これを防ぐにはコンテナ起動時にyarn install
を実行する必要があります。
たとえば以下のようにすることでコンテナ起動時にyarn install
を実行できます。
version: '3'
services:
web:
build: .
+ command: ["./start.sh"]
- command: ["rails", "server", "-b", "0.0.0.0"]
ports:
- '3000:3000'
volumes:
- .:/sample_app
depends_on:
- db
db:
image: mysql:8.0.21
command: --default-authentication-plugin=mysql_native_password
environment:
MYSQL_ROOT_PASSWORD: 'pass'
MYSQL_DATABASE: 'sample_app_development'
#!/bin/bash -eu
yarn
rails server -b '0.0.0.0'
シェルのパーミッションを変更します。
$ chmod 755 start.sh
コンテナを起動する際のコマンドでシェルを呼び出し、シェルの中でyarn install
を実行するようにしています。
yarn install
実行後、rails server
でRailsアプリケーションが起動するため先ほどのエラーが解消されます。
マイグレーションを自動実行できるようにする
テーブルのマイグレーションもyarn install
と同様、コンテナ起動時に呼び出されるシェルスクリプトで実行するようにします。
#!/bin/bash -eu
yarn
+ rails db:migrate
rails server -b '0.0.0.0'
しかし、docker-compose.yml
のdepends_on
を利用することでコンテナの起動順は制御できますが、コンテナの起動を待つということはできないため2、dbコンテナの起動準備が終わる前にdb:migrate
が実行されるとマイグレーションが正常に行われません。
マイグレーションの自動化をするにはdbコンテナの起動を待ってからdb:migrate
が実行されるようにする必要があります。
データベースの準備を待つ方法にはいくつかありますが、今回はwait-for-itを利用する方法を紹介します。
wait-for-it.sh
をカレントディレクトリに配置し、docker-compose.yml
を以下のようにするとデータベースの起動を待ってからスクリプトが実行されます。
version: '3'
services:
web:
build: .
- command: [""./start.sh"]
+ command: ["./wait-for-it.sh", "db:3306", "--", "./start.sh"]
ports:
- '3000:3000'
volumes:
- .:/sample_app
depends_on:
- db
db:
image: mysql:8.0.21
command: --default-authentication-plugin=mysql_native_password
environment:
MYSQL_ROOT_PASSWORD: 'pass'
MYSQL_DATABASE: 'sample_app_development'
まとめ
以上でRails 6 + MySQL 8のDocker環境の構築手順の紹介を終わります。
コンテナ起動時にシェルを実行することで、複数人でDocker環境を利用する場合でもスムーズに開発環境を構築できるようにしました。
なお、今回はコンテナ起動時にシェルを呼び出すことでyarn install
やdb:migration
を確実に実行するようにしましたが、コンテナ起動時のコマンドの制御はEntrykitと呼ばれるツールでも行えます。
EntrykitについてはRailsのDocker環境にEntrykitを導入し、bundle installを自動実行させる方法で紹介していますので、興味のある方はご覧になってください。
- 今回のまとめ
- Rails 6からはyarnのインストールも事前に行う必要がある
- MySQL 8の認証エラーが発生した場合は標準の認証に戻すことで解決する
- コンテナ起動時にシェルを実行することでテーブル作成等の自動化が可能になる
さいごに
Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!
参考記事
- Entrykit のすすめ
- 丁寧すぎるDocker-composeによるrails5 + MySQL on Dockerの環境構築(Docker for Mac)
- DockerでRuby on Railsの環境構築を行うためのステップ【Rails 6対応】
- DockerでのRuby on Rails環境構築を一つずつ詳解する
- Rails6の開発環境をDockerで構築しようとしたらドハマりした件