DockerComposeでRuby on Rails6とMySQL8を組み合わせて動かす
ちょっとwebアプリの検証をしたくてDockerCompose上にRails6 x MySQL8環境を作ろうと思ったのですが、案外ハマりどころが多かったのでメモに残しておくことにしました。
途中、たびたびpwd
で作業ディレクトリを確認していますが、どこで実行しているのか迷わないようにするための解説的な意味合いが強いので、理解できている人は無視してください。
作業ディレクトリの作成〜下準備
作業ディレクトリを作って入る
メインになるプロジェクトのディレクトリと、ソースコードを放り込むディレクトリを作ります。
mkdir -p ~/docker-ruby/src
cd docker-ruby
touch Dockerfile
touch Gemfile
Gemfileを書く
最低限の内容をもったGemfileを書きます。
Gemfileは、Railsを入れるときに上書きされて内容が壊れてしまうので、使いまわせるようにDockefileと同じディレクトリにオリジナルを入れるようにしています。
また、Gemfileを配置したらtouch
コマンドを使って空のGemfile.lock
を作っておきます。
pwd #=> ~/docker-ruby
vi Gemfile
cp Gemfile src/Gemfile
touch src/Gemfile.lock
Gemfileの内容はこんな具合です。
バージョンを6系で固定する記述をしても良いと思いますが、メジャーバージョンが上がるのは年単位で先の話なので、今は無視しています。
source 'https://rubygems.org'
gem 'rails'
Dockerfileを書く
せっかくなのでRuby 2.7を使うことにしました。
ハマりどころとして、Rails6からはWebpackerが標準で入ってくるので、yarnを整える必要がああります。
そのため、yarnを導入するためのコマンドをズラズラと記載しています。
yarnの導入については、もっと良いやり方があるかもしれないです
FROM ruby:2.7
# シェルスクリプトとしてbashを利用
RUN rm /bin/sh && ln -s /bin/bash /bin/sh
# 必要なパッケージのインストール
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs
# yarnパッケージ管理ツールインストール
RUN export APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=1 && apt-get update && apt-get install -y curl apt-transport-https wget && \
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-get update && apt-get install -y yarn
# 作業ディレクトリの作成と設定
RUN mkdir /app
ENV APP_ROOT /app
WORKDIR $APP_ROOT
# ローカルで作ったGemfileをコンテナ内の作業ディレクトリに配置する
ADD ./src/Gemfile $APP_ROOT/Gemfile
ADD ./src/Gemfile.lock $APP_ROOT/Gemfile.lock
# Gemfileのbundle install
RUN bundle install
ADD src $APP_ROOT
docker-compse.ymlを用意する
閑話休題。KubernetesとかRailsとかdoocker-composeとかを扱っていると一番多く読み書きするのがソースコードじゃなくてYAMLのファイルになりがちだと思いませんか?
さて、docker-compose.ymlファイルを作ります。
pwd #=> ~/docker-ruby
vi docker-compose.yml
データベースにはMySQL8の公式イメージをそのまま使うことにしました。
また、DB_USER
とDB_PASS
という名称で環境変数をexportして、docker-compose.yml内に秘密にしておくべき情報を記録しないようにしています。(今回の例だとDB_USERはデータベース名としても使われるので、一旦root固定です)
export DB_USER="root"
export DB_PASS="db_password"
二つ目のハマりポイントがここにあって、Docker + MySQL8の環境だと、こちらのQiita記事にあるようにcaching_sha2_password
の認証形式に対応できません。
先のQiita記事では設定ファイルをマウントさせていますが、できれば外側にファイルを用意したくないので、MySQLにコマンドラインオプションを渡してAuthentication Pluginを変更して運用するようにしています。
version: '3'
services:
db:
image: mysql:8.0
environment:
MYSQL_DATABASE: ${DB_USER}
MYSQL_ROOT_PASSWORD: ${DB_PASS}
ports:
- "3306:3306"
command: --default-authentication-plugin=mysql_native_password
app:
build: .
command: rails s -p 3000 -b '0.0.0.0'
volumes:
- ./src:/app
environment:
- DB_USER=${DB_USER}
- DB_PASS=${DB_PASS}
ports:
- "3000:3000"
links:
- db
これで大体の準備が完了しました。
環境のビルド〜データベース設定の修正
rails newして下地を作る
rails new
します。このときにコンテナがビルドされるので、ちょっと時間がかかります。
どうでもいいですけど、nokogiri
やsassc
等のnative extensionの導入は、もっと高速になって欲しいと思っています。
docker-compose run app rails new . --force --database=mysql --skip-bundle
ここで、newするときに--skip-bundle
が付いていると
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.
などとエラーが出てしまいますが、一旦無視して先に進みます。
database.ymlの編集
ビルドできたら、データベースの設定ファイルを編集します。
ちなみに、ファイルの編集コマンドにvi
を使っていますが、実際はVSCodeを使って作業しています。
pwd #=> ~/docker-ruby
vi src/config/database.yml
docker-compose.yml
でDBのユーザとパスワードを環境変数として定義しているので、database.yml
でもそれを使うようにします。
ということで、下記の要領でdefault
部分の設定を書き換えます。
(host
にdocker-compose.yml
で指定したデータベースコンテナの名前を与えるのを忘れないようにします。)
default: &default
adapter: mysql2
encoding: utf8mb4
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
username: <%= ENV.fetch("DB_USER") { "root" } %>
password: <%= ENV.fetch("DB_PASS") { "" } %>
host: db
コンテナビルドをしなおして、webpackerを導入する
設定も終わったのでコンテナをビルドしなおしてから、webpackerのインストールを行います。
(webpackerのインストールも結構時間がかかるので、気長に待ちます。)
最後のハマりポイントがここで、webpackerのインストールを忘れるとappコンテナを起動しても、何かしようとした途端に落ちてしまいます。(単にdocker-compose up
しただけだと普通に動いているように見える(UP状態に見える)のがたちの悪い所です)
pwd #=> ~/docker-ruby
docker-compose build
docker-compose run app rails webpacker:install
appコンテナのビルドに際して、下記のようにtzinfo-dataのエラーが出ますが、私と同じDocker for macを使っている環境ならば無視してしまって大丈夫だと思います。(エラーメッセージにある通り、x86-mingw32/x86-mswin32/x64-mingw32/javaの各環境で依存関係が生じるものです)
The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`.
また、docker-composeコマンドはよく使うので、適当にエイリアスをつけておくと良いと思います。
DBをつくって動作確認する
Railsを通じてDBを作ります。ここまで来ればもう難しいことはないです。
pwd #=> ~/docker-ruby
docker-compose down # 一回すべてのコンテナを落とす
docker-compose up -d
docker-compose ps #=> コンテナがUpしていることを確認する
docker-compose exec app rails db:create
出来上がったらhttp://0.0.0.0:3000
にアクセスして動作確認します。
Yay!ページが表示されたら動作確認完了です。
これでDockerComposeとRails6 x MySQL8を組み合わせた環境ができあがりました!
最終的なディレクトリの姿
最後に、参考程度に最終的なディレクトリの姿を掲載しておきます。
srcディレクトリ以下にrailsのコードがまとまって、それより上のディレクトリにコンテナを動かすためのファイル群がまとまっているという感じで整理できているのがわかると思います。
pwd #=> ~/docker-ruby
tree -L 2
.
├── Dockerfile
├── Gemfile
├── docker-compose.yml
└── src
├── Gemfile
├── Gemfile.lock
├── README.md
├── Rakefile
├── app
├── babel.config.js
├── bin
├── config
├── config.ru
├── db
├── lib
├── log
├── node_modules
├── package.json
├── postcss.config.js
├── public
├── storage
├── test
├── tmp
├── vendor
└── yarn.lock