はじめに
第二回目の今回は、Ruby on RailsをDockerコンテナで起動させるHello worldをやっていきます。
今日のゴール
- Ruby on Rails on Docker で Hello world する
では早速、Ruby on Rails on Docker で Hello world していきましょう!
Hello, Ruby on Rails on Docker
今回は↓の図のように、Docker 上に Rails アプリケーション用のコンテナと PostgreSQL (database) 用のコンテナを作っていきます。
まず、作業用のディレクトリを作っておきましょう。
$ mkdir Handson
$ cd Handson
今後、このHandson
ディレクトリをホームディレクトリとして話を進めますので、特に指定がない場合、Handson
ディレクトリでコマンドを叩いたり、Handson
ディレクトリから見た相対パスでファイルを編集していると思ってください。
では早速、Ruby on Rails on Docker な環境を構築するために以下の4つのファイルを作成していきます。
-
Dockerfile
: Rails アプリ用の Docker image の元となる設計図 -
Gemfile
: Rails アプリに必要なgem
を記載するファイル -
Gemfile.lock
:Gemfile
によってインストールされたgem
のバージョン情報などを管理するファイル -
docker-compose.yml
: 今回のアプリをコンテナ起動させるための Dcoker Compose ファイル
Dockerfile
FROM ruby:2.6.5-alpine3.11
ENV HOME="/app"
ENV LANG=C.UTF-8
ENV TZ=Asia/Tokyo
WORKDIR $HOME
RUN apk update && \
apk upgrade && \
apk add --no-cache \
gcc \
g++ \
less \
libc-dev \
libxml2-dev \
linux-headers \
make \
nodejs \
postgresql \
postgresql-dev \
tzdata \
yarn && \
apk add --virtual build-packs --no-cache \
build-base \
curl-dev
COPY Gemfile $HOME
COPY Gemfile.lock $HOME
RUN bundle install && \
apk del build-packs
COPY . $HOME
EXPOSE 3000
CMD ["rails", "server", "-b", "0.0.0.0"]
Dockerfile は初めて見る人にとっては「なんだこれ?」なものな気がしますが、読んでみると意外とシンプルです。
頭に大文字で書かれているのが命令
と呼ばれるものでコマンドみたいなものです。
今回の Dockerfile では以下の命令を利用しています。
-
FROM
: ベースイメージを定義する命令です。ruby イメージを指定しているので、元々 Ruby を使用できるイメージの上に Rails を動かす環境を作っていきます。 -
ENV
: 環境変数を定義する命令です。 -
WORKDIR
: 作業ディレクトリを定義する命令です。ベースイメージ内に該当のディレクトリがない場合は、そのディレクトリを作成することもしてくれます。 -
COPY
: ホストのファイルやディレクトリをイメージ内にコピーする命令です。 -
RUN
: コマンドを実行する命令です。 -
EXPOSE
: コンテナがリッスンするポートを宣言する命令です。 Rails ではデフォルトで 3000 番ポートを使用するので 3000 を指定してあげています。 -
CMD
: ソフトウェアを実行するためのコマンドを定義する命令です。コンテナが起動する時に実行されるコマンドといった方がイメージ湧きやすいかもしれません。少し特別な書き方(["xxx", "xxx"]
みたいな)をしますが、Rails アプリケーションを起動させるコマンドはrails server -b 0.0.0.0
でして、それをCMD
の記法で書いています。
あらかた命令と1行1行の内容について述べてしまいましたが、取りこぼしているところをキャッチアップ。
RUN
宣言でapk ~
と色々書いている9行目からの部分がありますが、apk add
は Alpine linux でパッケージをインストールするコマンドです。
apk update
でパッケージリポジトリの最新のインデックス(インストールできる最新バージョンは何か)を取得してきて、apk upgrade
ですでにインストールしているパッケージで最新版にアップデートできるものをアップデートします。その後、apk add
で Rails を起動するのに必要なパッケージをインストールしていきます。--no-cache
オプションはキャッシュを残さないようにするためのオプションです。不要なキャッシュを残さないことでコンテナ自体を軽量に保つことができます。(コンテナは軽量に保っておいた方がダウンロードに時間がかからなかったり、ホストのボリュームを圧迫しないのでよいとされています。)--virtual
オプションはそのインストールしたパッケージ達を一つのグループとして名前づけしています。今回の例だとbuild-packs
という名前をつけています。ここでインストールしたパッケージは Rails をビルドする上では必要なのですが起動させるためには不要なのであとでapk del
で削除するために名前づけしています。
その後、COPY
命令でGemfile
、Gemfile.lock
をイメージ内にコピーして、bundle install
を実行してます。bundle install
はGemfile
の内容に沿ってgem
をインストールするコマンドです。Rails 自体もgem
でインストールできるのでGemfile
にrails
を記入しておけばこのbundle install
の際にインストールされます。Gemfile.lock
はすでにインストール済のgem
のバージョンなどを管理して無闇にバージョンアップさせないようにしてくれます。
最後にCOPY . $HOME
でローカルホストのファイルを一式イメージ内にコピーすることで Rails アプリケーションを起動させられる Docker image を作ることができます。
Gemfile
source 'https://rubygems.org'
gem 'rails', '~>6'
Gemfileはかなりシンプルで、インストールするgem
のソースとrails
gem をインストールすることを定義しています。Rails は2020.02.02時点で最新バージョンが 6.0.2 なのでまぁメジャーバージョンとして 6 のものをインストールしてくださいというような指定の仕方をしています。
Rails アプリケーションでは最初rails new
コマンドでアプリに必要なgem
やファイルをインストール・生成するため、初期ではこれほどシンプルなGemfile
があるだけで構わないのです。
Gemfile
でバージョンを指定する表現方法はいくつかあります。gem
はGitHubなどで公開されていることが多くて大体 README でこう Gemfile に記載してくれと書かれていることが多いのであんまり気にすることはないかもしれませんが一応紹介。
-
gem 'rails', '6.0.0'
: 絶対 6.0.0 をインストール(バージョンを定義) -
gem 'rails', >= 6.0.0'
: 6.0.0 より最新のものをインストール(最低バージョンを定義) -
gem 'rails', >= 6.0.0', < 6.0.2
: 6.0.0 以上 6.0.2 未満のバージョンをインストール(バージョンの範囲を定義) -
gem 'rails', '~> 6.0.0'
: 6.0.X のバージョンをインストール(マイナーバージョンを定義)
Gemfile.lock
Gemfile.lock
は最初にbundle install
されるときに書き込まれるので、最初は空ファイルで問題ありません。
$ touch Gemfile.lock
touch
コマンドはファイルの更新日時を現在時刻に更新するためのコマンドですが、ファイルが存在しない場合は空ファイルを生成してくれるのでGemfile.lock
を生成するために使いました。
docker-compose.yml
version: '3'
services:
db:
image: postgres:12.1-alpine
environment:
- TZ=Asia/Tokyo
volumes:
- ./tmp/db:/var/lib/postgresql/data
web:
build: .
volumes:
- .:/app
ports:
- 3000:3000
depends_on:
- db
まず、サービスとしてdb
とweb
の二つがあることがわかるかと思います。db
は文字通りデータベース用のコンテナ(サービス)、web
は Rails アプリケーションを動作させるコンテナ(サービス)です。db
コンテナではimage
にpostgres:12.1-alpine
を指定しています。
docker-compose.yml
については前回もお話したので、前回お話していない項目を中心にお話します。
-
environment
: コンテナを起動する時に環境変数としてセットする。この場合、TZ(タイムゾーン)を東京にしてる。 -
volumes
: コンテナからホストのディレクトリをマウントしている。左がホストのパス、右がコンテナ内のパス。ホストのパスはこのdocker-compose.yml
の場所からの相対パスで書いてます。こう書くことで簡単にいえば、ホストのパスとコンテナのパスを同期しているイメージになり、ホストでファイルを編集すればコンテナ内にも反映され、コンテナ内でファイルが編集されればホストのファイルにも反映されるという関係を気づけます。コンテナはステートレスなので一度コンテナを削除して新しくコンテナを起動させた場合、最初のコンテナ(削除したコンテナ)内で変更されたデータは全てなかったことになってしまうのですが、ホストのディレクトリに同期しておくことで次に起動するコンテナもそのディレクトリをマウントするのでデータが永続化されるようになります。 -
build
:build: .
でdocker-compose.yml
と同じディレクトリのDockerfile
をbuildしたイメージを使ってコンテナを起動するようになります。 -
depends_on
: コンテナの依存関係を定義します。今回の場合『web
はdb
に依存している』と定義していることになりますが、これは次の2つが実現されます。-
docker-compose up
した時に、db
コンテナが起動してからweb
コンテナを起動する -
docker-compose up web
でweb
コンテナを起動させようとした場合、db
コンテナも起動させる
-
Rails アプリケーションを新規作成
まずはrails new
コマンドで新規に Rails アプリを作成します。
$ docker-compose run --rm --no-deps web rails new . -fGTd postgresql
...
Webpacker successfully installed 🎉🍰
いろいろな要素のあるコマンドですね。ちょっと順を追って説明します。
まずこのコマンドの大枠はdocker-compose run [options] <service name> <command>
です。今回の例では、
- [options]:
--rm --no-deps
- <service name>:
web
- <command>:
rails new . -fGTd postgresql
となっています。docker-compose run
はdocker-compose.yml
の定義にそって対象のコンテナを立ち上げてその中でコマンドを実行し、実行後にコンテナを停止するコマンドです。つまり、web
コンテナを立ち上げてrails new . -fGTd postgresql
をweb
コンテナ内で実行してくれます。
--rm
オプションはコマンド実行後にコンテナを停止した後に削除までしてくれるオプションです。基本停止したコンテナは不要だと思うのでこのオプションをつけるのがよきかと思います。
--no-deps
オプションはdocker-compose.yml
でdepends_on
が定義されていたとしてもそれを無視してdocker-compose run
を実行することができます。
次にweb
コンテナで実行されるrails new . -fGTd postgresql
を見ていきましょう。
まずrails new
は Rails アプリケーションを新規作成するためのコマンドです。.
はアプリケーションを作成する場所を指していてカレントディレクトリ(コマンドが実行されたディレクトリ)を示しています。-fGTd
はオプションなので一つずつ紐解きます。
-
-f
: ファイルの上書きを強制する。Gemfile
などに上書きが走りますがいちいち Yes or No を聞かれないようにするためにつけています。 -
-G
: Gitの初期設定をスキップします。Rails 6 からなんかこのオプションをつけないとまともにrails new
できなかったのでつけてます。 -
-T
:minitest
という Rails でデフォルトでインストールされるテストフレームワークのインストールをスキップします。僕はRSpec
というテストフレームワークをよく使っているのでこのオプションをつけて無駄にminitest
がインストールされないようにしています。 -
-d
:-d <database name>
で利用するデータベースを指定します。今回はpostgresql
を指定。
Rails アプリケーションの新規作成ができたら一度イメージをビルドしておきましょう。
$ docker-compose build
...
Successfully tagged handson_web:latest
docker-compose build
コマンドはdocker-compose.yml
でDockerfile
からのビルドが必要なサービスのイメージビルドをすべて実行してくれます。今回はdb
は DockerHub のイメージを使っているのでweb
のみがビルドが必要なサービスとしてビルドされます。
DBの接続設定
ビルドが終わったら、Rails アプリケーションの DB 接続設定をコーディングしていきます。 DB の接続設定はconfig/database.yml
に記載します。 Rails では設定系のファイルはconfig
ディレクトリに格納されています。
...
default: &default
adapter: postgresql
encoding: unicode
+ host: db
+ username: postgres
+ password:
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
...
host
、username
、password
がデフォルトから新たに追加した項目です。
host
はDBのホスト名です。今 Rails アプリが稼働しているweb
コンテナはdocker-compose.yml
が作る Docker ネットワークの中にいます。この中ではサービス名で名前解決してコンテナが相互に接続することができます。つまりhost
としてdb
を設定することでdocker-compose.yml
でdb
のサービス名で定義されたコンテナに接続できるようになるのです。
username
とpassword
は何の値なのでしょうか?これはdb
コンテナのイメージで指定したpostgres
のデフォルト値です。password
は同じホストからのアクセスであれば省略が可能になっています。postgres
イメージのusername
とpassword
はそれぞれPOSTGRES_USER
とPOSTGRES_PASSWORD
の環境変数で定義することもできます。
データベースの設定はここまでです。このデフォルトの設定値が開発環境(development
)、テスト環境(test
)の設定値として反映されるようになっています。
データベースを作成する
データベースの接続設定を定義したので、データベースを実際に作成していきます。
Rails ではデフォルトで本番環境(production
)、開発環境(development
)、テスト環境(test
)の3つの環境(environment)が用意されています。特別に環境を指定しない場合、開発環境で挙動するようになっています。
データベースの作成はrails db:create
コマンドを使いますが、このコマンドは開発環境とテスト環境用にデータベースを作成してくれます。本番環境用のデータベースを作成する場合は、RAILS_ENV=production
をオプションとしてつけます。
今回はまず開発環境向けにデータベースの作成を行いたいので、以下のコマンドを実行します。
$ docker-compose run --rm web rails db:create
Hello Ruby on Rails on Docker!!
ここまでで Hello world に必要な作業は全て完了しました。
コンテナを立ち上げて Hello world ページが表示されることを確認しましょう。
$ docker-compose up -d
Rails アプリは少し起動に時間がかかります。すぐにhttp://localhost:3000
にアクセスしてもまだアプリケーションが起動していないこともありますので、その場合はdocker-compose logs
コマンドを使ってアプリケーションの起動状態を確認してみましょう。
$ docker-compose logs -f
-f
オプションはログの変化をリアルタイムでコンソールに表示するためのオプションです。Rails アプリが起動した場合
web_1 | => Booting Puma
web_1 | => Rails 6.0.2.1 application starting in development
web_1 | => Run `rails server --help` for more startup options
web_1 | Puma starting in single mode...
web_1 | * Version 4.3.3 (ruby 2.7.0-p0), codename: Mysterious Traveller
web_1 | * Min threads: 5, max threads: 5
web_1 | * Environment: development
web_1 | * Listening on tcp://0.0.0.0:3000
web_1 | Use Ctrl-C to stop
というログが表示されます。この表示を確認したらCtrl+C
でdocker-compose logs
から抜け出しましょう。
では、http://localhost:3000
にアクセスしてみましょう!
このようなページが表示されたでしょうか?このページが Rails アプリケーションの第一歩、つまり Hello world です。おめでとうございます!これでもう『Rails は Hello world までならやったことあります』と自慢することができます!
後片付け
ここまでできたら後片付けをしておきましょう。
このままではコンテナが起動しっぱなしになってしまうので、最後にコンテナを停止させておきます。
$ docker-compose down
これでコンテナを停止させたので、http://localhost:3000
にアクセスしても先ほどのページは表示されなくなっていることでしょう。
まとめ
今回は、Dockerfile
やdocker-compose.yml
などのファイルを作成し、Rails アプリケーションを稼働させられる Docker イメージ、Docker コンテナを作成してみました。
さらに、Rails アプリを新規作成して Hello world に成功しました!
まだまだアプリケーション開発のほんの入り口ですが、Docker を使って Web アプリを起動させることができただけでもかなり感動があると思いますし、ここまでさほど大変ではないことも感じてもらえたかなと思います。これこそが Rails や Docker の偉大なところですね。
次回は、scaffold
という Rails の便利機能を使って、サンプル Web アプリケーションを作ってみようと思います。このscaffold
で作成できるアプリケーションが Rails の基本的なアプリケーションの形になりまして、その中には Rails アプリケーションを語る上では避けられないRESTful
やMVC
の要素が詰まっていますので、その辺りも合わせて学んでいけるようにしようと思います。
では、次回も乞うご期待!ここまでお読みいただきありがとうございました!
Next: コーディング未経験のPO/PdMのためのRails on Dockerハンズオン vol.3 - Scaffold, RESTful, MVC - - Qiita