はじめに
DockerでRuby on Railsの環境構築をするにあたり、色々な記事を読み比べ、エラーが出ては周りのエンジニアとして活躍されている方達にアドバイスをいただきました。
そうしていくうちに自分なりの「解」を見出せたので、自分が忘れないように、また他の方々の参考になればと思い当記事を作成いたしました。
RailsとRubyのバージョンについて
画像引用元:Amazon 「パーフェクト Ruby on Rails 【増補改訂版】 」
上記画像の著書「パーフェクト Ruby on Rails 【増補改訂版】」で Ruby 2.6.6と Rails 6.0.3の組み合わせを推奨しているので、僕も同じバージョンで環境構築しました。
ただ、著書内には”一番新しいRubyが一番よいRubyなので、余裕がある方は新しいバージョンで試して欲しい”との書き込みもありましたので、すでに慣れている方はぜひトライしてみてください。
参考にした記事一覧
僕の場合は環境構築をするにあたり、1つの記事だけを参考にするのではなく、5〜6記事を比較しながら取捨選択しました。
最終的に参考にさせていただいた記事は以下の通りです。
参考記事①:「DockerでRuby on Railsの環境構築を行うためのステップ【Rails 6対応】」
参考記事②:「docker ruby(2.6.5)・mysql(5.6.47)・rails(6.0.0)の開発環境構築」
参考記事③:「【Rails6】Dockerによる開発環境構築」
参考記事④:「Ruby on Rails 6のDocker環境構築」
参考記事⑤:「Dockerを使ってRails6環境の構築をしてみる」
各記事の執筆者様には本当にお世話になりました。この場を借りて本人には届かないと思いますが謝辞を述べさせてください。 ありがとうございます!!!!!!
環境構築の手順
早速DockerでRailsの環境構築を行う手順を解説するのですが、以下を前提として進めさせていただきます。
・Macを使用している方
・Docker for Macのインストールが済んでいる方
また、環境構築を進めていく中でつまづいたところ(エラーなど)を、「※つまづいたところ」として修正した手順も載せています。もし、同じエラーが出た方は参考にしてください。
1. アプリのディレクトリを作成する
アプリを開発するディレクトリを作成します。(僕の場合、cdコマンドでデスクトップに移動し、デスクトップ直下に作成しました。)
ここでは例として、「myapp」という名前のディクレトリを作成します。
mkdir myapp
cdコマンドで、myappに移動します。
cd myapp
2. Dockerfileを作成する
移動したmyappディレクトリ内に、空のDockerfile
を作成します。
touch Dockerfile
このDockerfileに、Dockerイメージを構築するための手順を記載します。VScodeなどのテキストエディタまたはターミナル上でviコマンドを使用して以下のようにDockerfile
を編集しましょう。
FROM ruby:2.6.6
# Node.jsをインストール
RUN curl -sL https://deb.nodesource.com/setup_16.x | bash - && apt-get install -y nodejs
# 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 && \
apt-get update && apt-get install -y yarn
# gem
RUN mkdir /myapp
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
COPY . /myapp
# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000
# Configure the main process to run when running the image
CMD ["rails", "server", "-b", "0.0.0.0"]
FROMやRUN、WORKDIR、COPYなどの先頭のコマンドについては、それぞれ何の意味があるのか検索して調べることをおすすめしますが、ざっくり解説すると以下の通りです。
FROM 使用するベースイメージ(ここではRuby)とバージョンの指定
RUN コマンドの実行。パッケージのインストールなどで使用
WORKDIR WORKは作業、DIRはディレクトリ、つまり「作業ディレクトリ」の変更。ターミナルでのcdと一緒
COPY 指定したローカル内のファイル・ディレクトリをコンテナ内にコピー
ENTRYPOINT docker run時に最初に実行されるコマンド(ここではentrypoint.sh
を参照)
EXPOSE 公開するポートの指定(http://localhost:3000/ の3000)
CMD docker run時に実行されるコマンド
ちなみに、Rails 6系からはwebpackを利用するので、Node.jsとYarnもインストールする必要があるので、RUN curlでインストールしています。
※つまづいたところ
別記事ではnodeのインストールのコマンドが、「https://deb.nodesource.com/setup_7.x 」となっていて、そのまま実行したら以下のエラーが出ました。
Installing dev server for live reloading
run yarn add --dev webpack-dev-server from "."
yarn add v1.22.18
[1/4] Resolving packages...
[2/4] Fetching packages...
error http-proxy-middleware@2.0.6: The engine "node" is incompatible with this module. Expected version ">=12.0.0". Got "10.24.0"
error Found incompatible module.
なので、僕のDockerfileでは「setup_7.x」の部分を「setup_16.x」に変更しています。
3. Gemfileを作成する
myappディレクトリ内に、空のGemfile
を作成します。
touch Gemfile
生成したGemfile
を以下のように編集します。
source 'https://rubygems.org'
gem 'rails', '6.0.3'
インストールするRailsのバージョンを指定しています。このGemfile
の内容は後にrails newを実行した際に中身が書き換えられます。
4. Gemfile.lockを作成する
myappディレクトリ内に、空のGemfile.lock
を作成するだけで大丈夫です。
touch Gemfile.lock
5. entrypoint.shを作成する
まずmyappディレクトリ内に、空のentrypoint.sh
を作成します。
touch entrypoint.sh
作成した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 "$@"
各内容について簡単に解説すると以下の通りです。
[set -e] エラーが発生するとスクリプトが終了される
[rm -f /myapp/tmp/pids/server.pid] server.pidファイルが存在している場合に削除する。
[exec "$@"] Dockerfile
のCMDで渡したコマンド(rails server -b 0.0.0.0)を実行
server.pidファイルはなんぞやって方はご自身で調べていただけたらと思います。
ただ、entrypoint.sh
に上記記述をしないと、後々docker-compose up -dでコンテナを立ち上げる時、「なぜかコンテナがExitになっていて立ち上がらない!!汗」って事件が起きるんですよ...。
その諸悪の根源がserver.pidファイルが存在しているせいってことが多いんですよね。それをentrypoint.sh
で削除するように指定しているわけですね。
※つまづいたところ
entrypoint.sh
でも実はつまづいたところがありまして、entrypoint.sh
の一行目のコメントである
#!/bin/bash
こいつを不要なものだと思って削除してたんですよ最初。
あとあと以下のエラーが出て、
standard_init_linux.go:228: exec user process caused: exec format error
ERROR: 1
調べてみるとどうやら、entrypoint.shファイルからshebang(実行時にインタプリタを指定するもの)が抜けていたことが原因らしく、
一行目の #!/ bin/shはただのコメントではないので削除してはいけないらしいんですよ。
参考記事:「exec user process caused “exec format error”で少し詰まった」
6. docker-compose.ymlを作成する
まずmyappディレクトリ内に、空のdocker-compose.yml
を作成します。
touch docker-compose.yml
次に作成したdocker-compose.yml
を以下のように編集します。僕はMySQLを利用するので以下のようにしました。
# docker-composeのバージョンを指定
version: "3"
services:
db:
# 使用するイメージを指定(dbでmysqlを指定しています)
image: mysql:8.0
# ディレクトリのマウント設定(dbデータなどを残せます)
volumes:
- ./tmp/db:/var/lib/mysql
# mysqlのパスワードを指定。環境変数を定義
environment:
MYSQL_ROOT_PASSWORD: password
# ポート番号の指定。[ホスト:コンテナ]で設定
ports:
- "3306:3306"
web:
# Dockerfileがあるパス
build: .
# 「server.pidファイルを削除」と「rails s」を実行するコマンド
command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
volumes:
- .:/myapp
- gem_data:/usr/local/bundle
ports:
- "3000:3000"
# 依存関係を設定。dbが起動してからwebへ起動することになる
depends_on:
- db
# データとgemを永続化する
volumes:
mysql_data:
gem_data:
PostgreSQLを利用する場合は、こちらの記事「Ruby on Rails 6のDocker環境構築」を参考にしてください。
7. Railsプロジェクトを作成する
これまで作成した5つのファイルをもとに、docker-compose runを実行してRailsアプリケーションを作成していきます。コマンドは以下の通りです。
docker-compose run --no-deps web rails new . --force --database=mysql
.
.
.
.
.
.
# 最後にWebpackerのインストール成功メッセージが表示される
Done in 115.80s.
Webpacker successfully installed 🎉 🍰
[--no-deps] リンクしたサービスを起動しない
[--force] 既存のGemfileを上書きするためのオプション
[--database=mysql] DBにmysqlを指定
上記コマンドを実行すると、Dockerfileを元にwebのイメージがビルドされ、生成されたコンテナ(web)の中でrails newされます。
上手く実行されればRails newした際に生成される各フォルダ・ディレクトリがあると思うので確認してください。
※つまづいたところ
rails newしたあとにGemfile
を確認したところ、railsを6.0.3で固定していたはずが以下のようになっていました。
gem 'rails', '~> 6.0.4', '>= 6.0.4.7'
また、後々解説するdocker-compose upでコンテナを起動させたところ、ログに以下のエラー文が出ていたので、
Bundler::GemNotFound: Could not find gem 'webpacker (~> 5.0)' in any of the gem sources listed in your Gemfile.
Railsのバージョンを6.0.3に固定するのと同時にwebpackerを~> 4.0から~> 5.0に変更しました。
gem 'rails', '6.0.3'
..
..
..
gem 'webpacker', '~> 5.0'
以下のコマンドでbundle updateして、
docker-compose run web bundle update
docker-compose downを行い、
docker-compose down
docker-compose up --buildを実行したところ、無事エラーがなくコンテナが起動できました。
docker-compose up --build
8. Dockerイメージをビルドする
先ほどのrails newコマンドによって、Gemfileが書き換わったので、Dockerイメージをビルドします。その際にbundle installもされます。
コマンドは以下の通りです。
docker-compose build
9. データベース接続情報を設定
データベースの情報を設定するために、config/database.yml
の中身をdocker-compose.yml
で設定したDB情報に書き換えます。
# 設定箇所のみ抜粋
default: &default
adapter: mysql2
encoding: utf8mb4
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
username: root
password: password
host: db
.
.
.
development:
<<: *default
database: myapp_development
.
.
.
test:
<<: *default
database: myapp_test
10. データベースを作成する
下記のコマンドでデータベースを作成します。
docker-compose run web bundle exec rails db:create
# 作成が成功すると、以下のコマンドが表示される。
Starting myapp_db_1 ... done
Created database 'myapp_development'
Created database 'myapp_test'
※つまづいたところ
docker-compose run web bundle exec rails db:create をしたところ、下記のエラーが出てしまいました。
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
Couldn't create 'myapp_development' database. Please check your configuration.
rails aborted!
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
こちらの記事「【docker】db:createすると、Plugin caching_sha2_password could not be loaded...のエラーハマった話」によると、原因は ”Mysql 8以降は認証プラグインの仕様が変わったため" とのことでした。
そのため、以下の手順でDocker内のmysqlにログインし、ユーザーのプラグインを変更していきます。
手順①:コンテナ内のMysqlにログインするため、コンテナIDを調べる
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8bb9c2d8f943 mysql:8.0 "docker-entrypoint.s…" 3 hours ago Up 7 minutes 0.0.0.0:3306->3306/tcp, 33060/tcp XXX-XXX_db_1
手順②:コンテナIDがわかったので、dbコンテナ内に入る
docker exec -it 8bb9c2d8f943 bash
手順③:mysqlに入る
# 上記のコマンドでdbコンテナ内に入ると、以下のようになっている
root@8bb9c2d8f943:/#
# 「mysql -uroot -p」コマンドを実行する
root@8bb9c2d8f943:/# mysql -uroot -p
手順④:mysqlのパスワードを入力
以下の画面が出たら、mysqlのパスワードを入力します。(僕の場合、config/database.yml
を見たらわかると思いますが、パスワードが「password」になっているのでこれを入力)
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 9
Server version: 8.0.23 MySQL Community Server - GPL
Copyright (c) 2000, 2021, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
手順⑤:現在のプラグインの状況を確認する
「 mysql >」と表示されるので、以下のコマンドを実行して、現在のプラグインの状況を確認します。
mysql> SELECT user, host, plugin FROM mysql.user;
# 以下が表示される
+------------------+-----------+-----------------------+
| user | host | plugin |
+------------------+-----------+-----------------------+
| root | % | caching_sha2_password |
| mysql.infoschema | localhost | caching_sha2_password |
| mysql.session | localhost | caching_sha2_password |
| mysql.sys | localhost | caching_sha2_password |
| root | localhost | caching_sha2_password |
+------------------+-----------+-----------------------+
プラグインの部分が「caching_sha2_password」になっていますね。これが原因でrails db:createでエラーが出たようです。
手順⑥:root user部分の2箇所だけプラグインを変更する
下記の2つのコマンドを実行して、root user部分の2箇所だけプラグインを変更します。('password'のところは手順④と同じDBのパスワードを入力します)
mysql> ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';
# 成功すると以下が表示される
Query OK, 0 rows affected (0.01 sec)
mysql> ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'password';
# 成功すると以下が表示される
Query OK, 0 rows affected (0.01 sec)
手順⑦:プラグインが変更できたか確認する
もう一度下記のコマンドを実行して、root user部分の2箇所のプラグインが変更されているか確認します。
mysql> SELECT user, host, plugin FROM mysql.user;
上手く変更できていれば、以下のようになっているはずです。
+------------------+-----------+-----------------------+
| user | host | plugin |
+------------------+-----------+-----------------------+
| root | % | mysql_native_password |
| mysql.infoschema | localhost | caching_sha2_password |
| mysql.session | localhost | caching_sha2_password |
| mysql.sys | localhost | caching_sha2_password |
| root | localhost | mysql_native_password |
+------------------+-----------+-----------------------+
5 rows in set (0.00 sec)
上手く変更できたら、まずexitコマンドでmysqlから抜けて、
mysql> exit
もう一度exitコマンドを実行してdbコンテナからも抜けましょう。
root@8bb9c2e8f963:/# exit
手順⑧:再度データベースを作成する
下記のコマンドで再度データベース作成を試みます。
$ docker-compose run web bundle exec rails db:create
# 作成が成功すると、以下が表示される。
Starting myapp_db_1 ... done
Created database 'myapp_development'
Created database 'myapp_test'
無事に成功されているはずです。
11. Dockerコンテナを起動し、ローカル環境のページにアクセスする
以下のコマンドでDockerコンテナを起動させましょう。
docker-compose up
ちなみに、以下のように「-d」オプションをつけてバックグラウンドで起動するようにするのが一般的です。
docker-compose up -d
バックグラウンドでコンテナが起動されるので、もしログを確認したい場合は「docker-compose logs」コマンドでログを確認できます。
ブラウザでhttp://localhost:3000 にアクセスして、以下の画面が表示されれば環境構築完了です。お疲れ様でした!
もし、上記画面が表示されなかった方は上手くコンテナが起動していない可能性があります。docker-compose psでコンテナの状況(ステータス)を確認してみましょう。
docker-compose ps
# 以下のようにコンテナの状況を確認できます。
Name Command State Ports
---------------------------------------------------------------------------------------------
myapp-db-2 docker-entrypoint.sh mysqld Up 0.0.0.0:3306->3306/tcp, 33060/tcp
myapp-web-1 entrypoint.sh bash -c rm - ... Exit 1
上記の場合、「myapp-web-1」のステータスが「Exit」になっていますね。なぜExitになっているかログを確認して原因を探る必要があります。
docker-compose upに「-d」オプションをつけてバックグラウンドでコンテナを起動した場合、docker-compose logsでログを確認することができます。
docker-compose logs
# 以下のようにwebとdbのログが確認できます。
myapp-db-2 | 2022-04-23 11:09:54+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.28-1debian10 started.
myapp-db-2 | 2022-04-23 11:09:54+00:00 [Note] [Entrypoint]: Switching to dedicated user 'mysql'
myapp-db-2 | 2022-04-23 11:09:54+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.28-1debian10 started.
..
..
..
..
..
..
..
myapp-db-2 | 2022-04-23T11:10:16.515523Z 0 [System] [MY-011323] [Server] X Plugin ready for connections. Bind-address: '::' port: 33060, socket: /var/run/mysqld/mysqlx.sock
myapp-db-2 | 2022-04-23T11:10:16.515596Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.0.28' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server - GPL.
myapp-web-1 | bundler: failed to load command: rails (/usr/local/bundle/bin/rails)
myapp-web-1 | Bundler::GemNotFound: Could not find gem 'webpacker (~> 5.0)' in any of the gem sources listed in your Gemfile.
myapp-web-1 | /usr/local/lib/ruby/2.6.0/bundler/resolver.rb:287:in `block in verify_gemfile_dependencies_are_found!'
myapp-web-1 | /usr/local/lib/ruby/2.6.0/bundler/resolver.rb:255:in `each'
myapp-web-1 | /usr/local/lib/ruby/2.6.0/bundler/resolver.rb:255:in `verify_gemfile_dependencies_are_found!'
myapp-web-1 | /usr/local/lib/ruby/2.6.0/bundler/resolver.rb:49:in `start'
..
..
..
..
..
..
..
myapp-web-1 | /usr/local/lib/ruby/2.6.0/bundler/setup.rb:20:in `<top (required)>'
myapp-web-1 | /usr/local/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
myapp-web-1 | /usr/local/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
上記の場合、gemのwebpackerのエラーが出てます。このエラーに関しては、環境構築手順の「7. Railsプロジェクトを作成する」の「※つまづいたところ」で修正方法を記載しています。
まとめ
いかがでしたか?無事環境構築できましたでしょうか?汗
ただ、もし失敗したとしてもエラーを読んでその都度検索して調べるのも重要だなと感じました。おかげで検索力がかなりつきました!
もしなにか間違いや質問、「こここうした方がいいよ!」などがあればコメントや修正リクエストお待ちしております!!
また、参考になった方はシェアやLGTM、ストックをしていただけるととても嬉しいです(土下座)