はじめに
この記事では、Ruby on Railsのアプリを開発する際に必要となるDockerfileやcompose.ymlといったファイルをコピペではなくゼロから作成した手順を紹介します。環境構築の際に必要な情報はググればたくさんヒットするのですが、なぜそのような書き方をするのかを理解しないままコピペして使うのは個人的に嫌だったため、本記事を執筆するに至りました。
使用するDockerのバージョンは次の通りです。
docker --version
# Docker version 27.1.1, build 6312585
本記事を参考に環境構築を行う場合、最終的に必要な手順は最終節に書かれているので、お急ぎの方はそちらをご覧ください。
コンテナ上で試行錯誤してみる
ゼロからいきなりDockerfile、compose.ymlを書くのはさすがに難しいです。そこで、まず最初に、Railsアプリを立ち上げるまでに必要なプロセスをコンテナ上で確認します。
コンテナを立ち上げるためのコマンドは次の通りです。
docker run -it -p 3000:3000 ruby:3.3.4-slim-bookworm bash
ベースイメージのバージョンは本記事執筆時の最新安定版です。今回使用しているベースイメージruby:3.3.4-slim-bookworm
のサイズは約210MBで、slimでないruby:3.3.4-bookworm
はサイズが約1GBになります。どちらを使ってもいいのですが、イメージサイズの小さい方がビルドにかかる時間を節約できるので、今回はruby:3.3.4-slim-bookworm
をベースイメージとしています。(ただしruby:3.3.4-bookworm
をベースイメージにするとこの後のプロセスのいくつかを省略することができます。)
コンテナを立ち上げた時点でrubyとBundlerを使用することができます。
ruby --version
# ruby 3.3.4 (2024-07-09 revision be1089c8ec) [aarch64-linux]
bundle --version
# Bundler version 2.5.11
rails
gemをインストールする
まず最初にRailsアプリを作成するディレクトリapp
を作ります。
mkdir /app
cd /app
続いてapp
ディレクトリでGemfile
を作成します。
bundle init
# Writing new Gemfile to /app/Gemfile
Gemfile
を編集したいので以下の手順でvimをインストールします。
apt-get update
apt-get install -y vim
vimでGemfile
の最終行のコメントアウトを解除します。
# frozen_string_literal: true
source "https://rubygems.org"
gem "rails"
このGemfile
をもとにrails
gemをインストールします。
bundle install
実はこのコマンドは失敗します。エラーメッセージの中に、
To see why this extension failed to compile, please check the mkmf.log which can be found here:
/usr/local/bundle/extensions/aarch64-linux/3.3.0/bigdecimal-3.1.8/mkmf.log
という記述があるので、このログファイルを見てみます。
cat /usr/local/bundle/extensions/aarch64-linux/3.3.0/bigdecimal-3.1.8/mkmf.log
# LD_LIBRARY_PATH=.:/usr/local/lib "gcc -o conftest -I/usr/local/include/ruby-3.3.0/aarch64-linux -I/usr/local/include/ruby-3.3.0/ruby/backward -I/usr/local/include/ruby-3.3.0 -I. -O3 -fno-fast-math -ggdb3 -Wall -Wextra -Wdeprecated-declarations -Wdiv-by-zero -Wduplicated-cond -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wwrite-strings -Wold-style-definition -Wimplicit-fallthrough=0 -Wmissing-noreturn -Wno-cast-function-type -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-packed-bitfield-compat -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wsuggest-attribute=format -Wsuggest-attribute=noreturn -Wunused-variable -Wmisleading-indentation -Wundef -fPIC conftest.c -L. -L/usr/local/lib -Wl,-rpath,/usr/local/lib -L. -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -Wl,-rpath,/usr/local/lib -L/usr/local/lib -lruby -lm -lpthread -lc"
# checked program was:
# /* begin */
# 1: #include "ruby.h"
# 2:
# 3: int main(int argc, char **argv)
# 4: {
# 5: return !!argv[argc];
# 6: }
# /* end */
#
どうやらgccコマンドを利用しようとしているようですが、そもそもslimイメージにはgccは入っていません。
gcc --version
# bash: gcc: command not found
そこでgccをインストールします。
apt-get install -y gcc
インストールが完了した後、途中で終わっていたbundle install
を再度実行します。すると今度は別のエラーが発生します。表示されるエラーメッセージを見ると、
make failedNo such file or directory - make
の記述があることがわかります。そこでmakeをインストールします。
apt-get install -y make
インストール完了後、再度bundle install
を実行すると、今度は成功し、Gemfile.lock
が生成されます。ちなみにインストールされたrailsのバージョンは本記事執筆時の最新版である7.2.0です。
なお今回gccとmakeをインストールしましたが、代わりにbuild-essentialというパッケージをインストールしても構いません。(そちらの方が一般的かと思います。)このパッケージにはgccやmakeを含む様々なライブラリが含まれています。
アプリに必要なファイルをrails new
で用意する
続いてRailsアプリを作成します。今回はDBにMySQLを使用します。(デフォルトではSQLiteです。)
rails new . --database=mysql
するとGemfileを上書きするかの確認を求められるので上書きします。
実はこの時点でもエラーがでます。これは、rails new
中に自動で実行されるgit init
が失敗するからです。
git --version
# bash: git: command not found
そこでgitを入れます。
apt-get install -y git
git自体はrailsに必須のものではないですし、rails new
の--skip-git
オプションでgit init
をスキップできます。しかしこのオプションは.gitignore
の自動生成もスキップしてしまいます。今回はこの.gitignore
を使用したいので--skip-git
オプションは用いないことにします。
gitを入れてもう一度rails new
を実行すると、bundle install
中にエラーが発生します。これはMySQLクライアントが入っていないためです。エラーメッセージに
mysql client is missing. You may need to 'sudo apt-get install libmariadb-dev', 'sudo apt-get install
libmysqlclient-dev' or 'sudo yum install mysql-devel', and try again.
とあるので、これを参考にしてMySQLクライアントをインストールします。まず今回はDebianを使用しているのでyum install mysql-devel
は使いません。またMariaDBとMySQLは微妙に違うので
apt-get install -y libmysqlclient-dev
を選択します。しかしこのコマンドを実行すると
Package libmysqlclient-dev is not available, but is referred to by another package.
This may mean that the package is missing, has been obsoleted, or
is only available from another source
However the following packages replace it:
libmariadb-dev-compat libmariadb-dev
E: Package 'libmysqlclient-dev' has no installation candidate
とエラーが表示され、MariaDBが提案されてしまいます。apt-cache search
でパッケージを検索するとlibmysqlclient-devではなくdefault-libmysqlclient-devがヒットします。
apt-cache search libmysqlclient-dev
# default-libmysqlclient-dev - MySQL database development files (metapackage)
このパッケージはMySQLクライアントライブラリの開発用ヘッダーファイルとライブラリを提供するようです。
ちなみにこのパッケージはRailsガイドにも載っています。このパッケージを次のコマンドでインストールします。
apt-get install -y default-libmysqlclient-dev
インストール完了後bundle install
を実行すると今度は成功します。
DBサーバーに接続
次のコマンドでサーバーを立ち上げます。
rails server -b 0.0.0.0
この-b
オプションを使用しないとコンテナ外からアプリにアクセスできません。
http://0.0.0.0:3000 にアクセスするとエラーが表示されます。
ActiveRecord::ConnectionNotEstablished (Can't connect to local server through socket '/run/mysqld/mysqld.sock' (2)):
これはMySQLサーバーが起動していないためです。そこでMySQL用のコンテナを立ち上げます。先に現在railsの環境構築を行なっているコンテナを止めてdocker commit
します。
docker commit <rails-container-id> myapp:version1
以下のコマンドでMySQLサーバーを立ち上げます。
docker network create myapp
docker run -d \
--network myapp --network-alias mysql \
-e MYSQL_ROOT_PASSWORD=secret \
mysql:9.0.1-oraclelinux9
次にrailsのコンテナを立ち上げます。
docker run -it -p 3000:3000 \
--network myapp \
-e MYSQL_PASSWORD=secret \
myapp:version1 bash
続いてconfig/database.ymlを編集してDBに接続するための情報を追記します。
cd app
vim config/database.yml
# 略
default: &default
adapter: mysql2
encoding: utf8mb4
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
username: root
- password:
+ password: secret
- host: <%= ENV.fetch("DB_HOST") { "localhost" } %>
+ host: mysql
# 略
最後に次のコマンドでDBを作成します。
rails db:create
# Created database 'app_development'
# Created database 'app_test'
その後、
rails server -b 0.0.0.0
でサーバーを起動し、http://0.0.0.0:3000 にアクセスすると、Railsの初期画面が出ます。
DBを操作できるようにする
次のコマンドでMySQLサーバーを立ち上げているコンテナに入ります。
docker exec -it <mysql-container-id> mysql -u root -p
このコンテナ上でDBの一覧を確認すると、先ほどrails db:create
で作成したDBが表示されます。
SHOW DATABASES;
+--------------------+
| Database |
+--------------------+
| app_development |
| app_test |
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
6 rows in set (0.00 sec)
ただ通常Railsアプリを開発中にDBを操作するときはrails db
コマンドを使用します。このコマンドは現時点では使用することができないので、追加で設定が必要です。
まず次のコマンドでRailsのコンテナに入ります。
docker exec -it -w /app <rails-container-id> bash
試しにrails db
コマンドを叩くとエラーが表示されます。
rails db
# Couldn't find database client: mysql, mysql5. Check your $PATH and try again.
MySQLクライアントが入っていないので、次のパッケージをインストールします。
apt-get install -y default-mysql-client
インストール完了後、rails db
を実行するとDBに接続することができます。
SHOW TABLES;
+---------------------------+
| Tables_in_app_development |
+---------------------------+
| ar_internal_metadata |
| schema_migrations |
+---------------------------+
2 rows in set (0.003 sec)
以上でRailsアプリをゼロから立ち上げるために必要なプロセスがわかりました。このプロセスをDockerfileまたはcompose.ymlに書き起こしていきます。
Dockerfile、compose.ymlを書いてみる
まず最初にDockerfileを用意します。基本的にコンテナ上で行ったコマンドを書き起こせば大丈夫です。
FROM ruby:3.3.4-slim-bookworm
RUN apt-get update && \
apt-get install -y --no-install-recommends \
gcc \
make \
git \
default-libmysqlclient-dev \
default-mysql-client && \
rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY Gemfile Gemfile.lock /app/
RUN bundle install
EXPOSE 3000
CMD [ "bash" ]
apt-get install
の--no-install-recommends
オプションは、あるパッケージをインストールするときに推奨パッケージもインストールされてしまうのを防ぎます。
またrm -rf /var/lib/apt/lists/*
はインストール時のキャッシュを削除します。これらの記述を追加することでイメージサイズの削減に繋がります。またapt-get update
とapt-get install
を一つのRUN句内に記述しているのはパッケージの最新バージョンが正しくインストールされるのを保証するためです。このあたりの話はDockerの公式ガイドに詳しい記述があります。
またGemfileは以下の内容で用意します。
# frozen_string_literal: true
source "https://rubygems.org"
gem "rails"
Gemfile.lockは空ファイルを用意します。
続いてcompose.ymlを用意します。ここにはdockerコマンドに渡したオプションを明記していくイメージです。
x-db-config: &db-config
MYSQL_ROOT_PASSWORD: secret
services:
web:
build: .
command: rails server -b 0.0.0.0
ports:
- "3000:3000"
volumes:
- ./:/app
environment:
<<: *db-config
depends_on:
- db
db:
image: mysql:9.0.1-oraclelinux9
volumes:
- db-data:/var/lib/mysql
environment:
<<: *db-config
volumes:
db-data:
ここでローカルのディレクトリとコンテナ上のディレクトリをバインドマウントしているので、コンテナを作成した後ローカルでファイルを編集すれば、その変更がコンテナにも反映されます。そのためDockerfileでvimをインストールする手順は省いています。
これらのファイル(Dockerfile、compose.yml、Gemfile、Gemfile.lock)を用意してコンテナを立ち上げます。まず次のコマンドでRailsアプリに必要なファイルを作成します。
docker compose run --rm web rails new . --database=mysql --skip-docker
--skip-docker
オプションはDockerfileの上書きと.dockerignoreなどのファイルの生成をスキップします。
このコマンドを実行するとGemfileとGemfile.lockが上書きされるので、それらを元にイメージを作成します。
docker compose build
続いてconfig/database.ymlを編集し、DBに接続できるようにします。
# 略
default: &default
adapter: mysql2
encoding: utf8mb4
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
username: root
- password:
+ password: secret
- host: localhost
+ host: db
# 略
続いてDBを作成します。
docker compose run --rm web rails db:create
以上で完成です。次のコマンドを実行するとWebサーバーとDBサーバーが立ち上がります。
docker compose up
コンテナ上でコマンドを実行するときは、例えば次のように実行すればいけます。
docker compose exec web rails db
おわりに
ここまで読んでくださりありがとうございました。何か不備があればコメントで教えてください。