はじめに
そうだ、Rails環境、作ろう。
(BGM: 「My Favorite Things / The Sound of Music」)
この記事の目標
-
docker compose up
したらホスト側からrailsのウェルカムページが表示できるコンテナイメージを作成するDokerfileおよびdocker-compose.ymlを作成する。 - DBは個人的な好みでpostgresqlにする(それほど特にこだわりはないけど)
- DBはホスト側のSQLツールから接続できるようにする。
- ホスト側の環境は極力汚さない。
作業環境
- Windows10 Pro(MacOSでも手順は変わらないと思います)
- Docker Desktop version 4.1.1
- Visual Studio Code 1.59.1
- 以下の機能拡張をインストールしておく。
- Remote Development
- Docker
作成手順
docker-compose.ymlの構成
コンテナを2つ作ることとします。
- web: railsのコンテナ
- db: postgresqlのコンテナ
ベースになるrubyコンテナの作成
docker-compose.ymlを作る予定のフォルダに、dockerfiles/web
フォルダを作成します。
コンテナのビルドに必要なファイルはここに入れることにします。
まずはDockerfileを公式のdocker composeチュートリアルを参考に作成します。
FROM ruby:2.7
RUN apt-get update -qq && apt-get install -y nodejs postgresql-client
RUN mkdir /myapp
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
source 'https://rubygems.org'
gem 'rails', '~> 5'
dockerfiles/web/Gemfile.lock は空のファイル。
とりあえずrailsをインストールできるところまでの記述です。
次にcompose用のファイルを作成します。同様にチュートリアルからの参照です。
version: '3'
services:
db:
image: postgres
volumes:
- dbdata:/var/lib/postgresql/data
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: myapp_development
POSTGRES_INITDB_ARGS: "--encoding=UTF-8"
ports:
- 5432:5432
web:
build: ./dockerfiles/web/
tty: true
volumes:
- myapp:/myapp
ports:
- "3000:3000"
depends_on:
- db
volumes:
dbdata:
myapp:
ポイントは、
- dbコンテナは環境変数でpostgresのユーザ名、パスワード、デフォルトデータベース名を記載しておく。
- dbコンテナのポートはホスト側からアクセスできるように空けておく
- dbのデータ格納場所とweb側のアプリデータ格納場所は
volumes
で永続化する。その際ホスト側にマウントはしない。 - web側のポートも同様にrails内部サーバデフォルトの3000を開けておく。
- web側は
tty: true
を指定しておく。後からコンテナのシェルにアタッチしたいため。
rails側コンテナの整備
ここで一度docker compose up -d
してコンテナをビルドし実行します。
無事エラーなくコンテナがupできたら、vscodeのdockerタブから今立ち上がったwebコンテナを右クリックしvscodeでアタッチします。
新しくvscodeのウィンドウが開き、必要な機能拡張がコンテナ側にインストールされていきます。
インストールが完了したら、railsアプリを入れる予定の /myapp
ディレクトリを「フォルダーを開く」ボタンから選択して開きましょう。
新しいターミナルを開き、クイックスタートの内容に沿ってrailsアプリを初期化します。
$ rails new . --force --database=postgresql
すると、こんなメッセージが表示されます。
Bundler could not find compatible versions for gem "sprockets":
In snapshot (Gemfile.lock):
sprockets (= 4.0.2)
In Gemfile:
sass-rails (~> 5.0) was resolved to 5.1.0, which depends on
sprockets (< 4.0, >= 2.8)
rails (~> 5.2.6) was resolved to 5.2.6, which depends on
sprockets-rails (>= 2.0.0) was resolved to 3.4.0, which depends on
sprockets (>= 3.0.0)
Running `bundle update` will rebuild your snapshot from scratch, using only
the gems in your Gemfile, which may resolve the conflict.
run bundle exec spring binstub --all
bundler: command not found: spring
Install missing gem executables with `bundle install`
使用するモジュール間で依存するモジュールのバージョンにコンクリフトがあるようです
(sass-railsとrailsで共に使用しているsproketsのバージョンコンクリフトの模様)
解決するには bundle update
しなさい、と書かれているので実際に実行します。
なお、このコマンドの終了コードは"0"で、すなわちエラー終了ではありません(重要!)
$ bundle update
ずらずらとログが流れますが特に問題なく終了します。
これでアプリフォルダの作成は終了したので、実際にサーバを起動してウェルカムページが表示されるかチェックします。
$ rails server -b 0.0.0.0
-b 0.0.0.0
は必ず指定します。そうしないとコンテナ内部からしか接続できません。ホスト側から接続しないなら0.0.0.0
でListenしなければなりません。
無事サーバが起動したら、ホスト側から http://localhost:3000/ にアクセスして動作チェックします。
エラーが出ました。postgresに接続できないエラーのようです。
それもそのはず、今回postgresはdbコンテナを別で用意していますが、railsのデフォルトのDB接続先はlocalhostです。
config/database.yml
を修正してデータベースの情報を追記しなければなりません。
(抜粋)
# Configure Using Gemfile
# gem 'pg'
#
default: &default
adapter: postgresql
host: db
username: postgres
password: postgres
encoding: unicode
# For details on connection pooling, see Rails configuration guide
# http://guides.rubyonrails.org/configuring.html#database-pooling
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
host
username
password
を追記しました。
この3つはdocker-compose.yml
でdbコンテナに指定した環境変数の値に合わせます。
この変更を保存してrailsサーバーを再起動することにより、無事にウェルカムページが表示されます。
このdatabase.ymlを変更する作業は rails new
した後、つまりconfig/database.ymlが作成された後であれば bundle update
する前に行っても問題ありません。
コンテナup後に実行した変更をDockerfileに反映させる
up後に実行した内容は以下の通りです。
rails new . --force --database=postgresql
- config/database.yml の書き換え
bundle update
- railsサーバ起動
この作業をDockerfileに反映させます。
database.yml の取得
vscodeのエクスプローラーからdatabase.ymlを右クリックし、「ダウンロード」を選択するとホスト側にファイルを転送できます。
dockerfiles/web
フォルダ内に保存しましょう。
Dockerfileの書き換え
Dockerfileを以下の様に書き換えます。
FROM ruby:2.7
RUN apt-get update -qq && apt-get install -y nodejs postgresql-client
RUN mkdir /myapp
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
# ここまでが既存作業分
# ここからが追加作業分
RUN rails new . --force --database=postgresql
# bundle update前にhost等を書き換えたdatabase.ymlに差し替える
COPY database.yml /myapp/config/database.yml
RUN bundle update
# entrypointのヘルパースクリプトを追加する
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
# portのEXPOSEを忘れずに
EXPOSE 3000
# up時にサーバーが起動状態になるようCMDを設定
CMD ["rails", "server", "-b", "0.0.0.0"]
entrypoint.sh には不意にコンテナが正常終了しなかった場合、railsのserver.pidが残るので再起動前に削除するために配置します。
#!/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 "$@"
RUNコマンドは終了コードが0以外だとコンテナのbuild時にそこでコンテナの構築が停止してしまいます。
先ほどの rails new
コマンドはエラーのようなメッセージは出ましたが終了コードが0なのでそのまま次のコマンドを実行することができます。
Dockerfileを書き換えたらコンテナをdownし、再ビルドします。
念のため永続化ボリュームもいったん削除して再構築します。
docker compose down -v
docker compose build --no-cache
buildが無事終了したら、upして http://localhost:3000/ へアクセスして無事ウェルカムページが表示できるかどうか確かめてみましょう。
表示できたら環境構築成功です。お疲れさまでした。
環境構築後
rails new
やbundle update
するためにvscodeからwebコンテナにアタッチしました。以後はvscode内でrailsコマンドを実行したり、通常の開発と同じようにvscodeのエクスプローラからファイルの作成等がローカル側での開発と同様に可能となります。
ホスト側にあるファイルもvscodeのエクスプローラにドラッグ&ドロップすれば簡単にコピーできます(逆にコンテナ側からホスト側へのファイルコピーは右クリック→ダウンロードになります)
vscodeを使い慣れていればローカルで開発するのと同じような感覚でコーディングが可能です。
おわりに
今回は「一発でrailsサーバーが起動するDocker環境構築」を行いました。
今回のrails環境構築の様に、単にRUNコマンドでインストールしていくだけでは環境構築がうまくいかない場合、エラーが出ないところまで環境構築し、その後はコンテナ側で追加のインストール作業を行い、その作業結果をDockerfileにフィードバックする方法を取ると良いでしょう。
他のコンテナの環境構築にも十分応用できると思います。
おまけ:今回作成したファイル一式
githubに公開しています。
https://github.com/haruyan-hopemucci/docker-rails5-postgres