Open9

Ruby on Rails 6のDocker環境構築

Yuma ItoYuma Ito

1. Dockerfile, docker-compose.ymlを用意する

Rails 6からはwebpackを利用するので、Node.js, Yarnもインストールする必要がある。

Dockerfile
FROM ruby:3.0
RUN apt-get update -qq && apt-get install -y postgresql-client
WORKDIR /myapp

# install nodejs(LTS)
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - && apt-get install -y nodejs

# install yarn
RUN npm install --global yarn

# gem
COPY Gemfile* /myapp/
RUN bundle install

# 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"]

参考:

PostgreSQLを利用する場合

docker-compose.yml
version: "3.9"
services:
  db:
    image: postgres
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: # 任意
      POSTGRES_PASSWORD: # 必須
      POSTGRES_DB: # 任意
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db

MySQLを利用する場合

docker-compose.yml
version: "3.9"
services:
  db:
    image: mysql:8.0
    volumes:
      - mysql-data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: 任意
      MYSQL_DATABASE: 任意
      MYSQL_USER: 任意
      MYSQL_PASSWORD: 任意
    ports:
      - "3306:3306"
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/app
    ports:
      - "3000:3000"
    depends_on:
      - db
volumes:
  mysql-data: null

Yuma ItoYuma Ito

2. 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 "$@"
Yuma ItoYuma Ito

3. Gemfile, Gemfile.lockを生成する

touch Gemfile Gemfile.lock

Gemfileは以下のようにRailsのバージョンを指定する。

Gemfile
source 'https://rubygems.org'
gem 'rails', '~> 6'

コマンドでさくっと。

echo "source 'https://rubygems.org'
gem 'rails', '~> 6'" > Gemfile
Yuma ItoYuma Ito

4. Railsプロジェクトを作成する

DBはPostgreSQLを利用。

docker-compose run --no-deps web rails new . --force --database=postgresql

--no-depsdocker-compose runのオプション。依存先のコンテナを起動しない。

MySQLを利用する場合

docker-compose run --no-deps web rails new . --force --database=mysql

APIとして利用する場合は、--apiオプションをつける。APIとして不要なファイルが生成されない。

docker-compose run --no-deps web rails new . --force --database=postgresql --api

5. Dockerイメージをビルド

Gemfileが書き換わったので、Dockerイメージをビルドして作成する。

docker-compose build
Yuma ItoYuma Ito

6. DB接続情報を設定

config/database.ymldocker-compose.ymlで設定したDB情報で書き換える。

database.yml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
+  username: <%= ENV["MYSQL_USER"] %>
+  password: <%= ENV["MYSQL_PASSWORD"] %>
+  host: <%= ENV["MYSQL_HOST"] %>

development:
  <<: *default
+  database: <%= ENV["MYSQL_DATABASE"] %>

※環境変数へのアクセスは <%= ENV["ENV_NAME"] %>

※ファイルの権限に注意。rootユーザーで作成されているので、通常のユーザーでは書き換える権限がない。VSCodeのRemote Containerでコンテナに入って変更するか、chmodコマンドで権限を与える(これが適切かどうかは、、、)。

sudo chmod -R o+w .

7. Dockerコンテナを立ち上げる

docker-compose up

以下のようなログが表示されたら、立ち上げ成功。

web_1  | => Booting Puma
web_1  | => Rails 6.1.4.1 application starting in development 
web_1  | => Run `bin/rails server --help` for more startup options
web_1  | Puma starting in single mode...
web_1  | * Puma version: 5.4.0 (ruby 3.0.2-p107) ("Super Flight")
web_1  | *  Min threads: 5
web_1  | *  Max threads: 5
web_1  | *  Environment: development
web_1  | *          PID: 1
web_1  | * Listening on http://0.0.0.0:3000
web_1  | Use Ctrl-C to stop

8. サーバーに接続する

ブラウザから http://localhost:3000 に接続してRuby on Railsのトップページが表示されたら、OK

Yuma ItoYuma Ito

システムテストを実行しようとしたら Selenium::WebDriver::Error::WebDriverError: Unable to find chromedriver エラー

前提

  • minitest (5.15.0)
  • capybara (3.36.0)
  • selenium-webdriver (4.1.0)

原因

RailsがあるDockerコンテナ内部に ChromeのWebDriverがインストールされていないため。
Dockerfileでインストールしても良いが、システムテスト用のChrome用コンテナを立ち上げることにした。

対応

使用するDockerイメージはselenium/standalone-chrome-debugにした。

docker-compose.yml
services:
  web: # Rails用
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    environment:
      MYSQL_USER: huser
      MYSQL_PASSWORD: password
      MYSQL_HOST: db
      MYSQL_DATABASE: app_development
+      SELENIUM_DRIVER_URL: http://selenium_chrome:4444/wd/hub
    volumes:
      - .:/app
    ports:
      - "3000:3000"
    depends_on:
      - db
+      - selenium_chrome
+  selenium_chrome: # システムテスト用
+    image: selenium/standalone-chrome-debug:3.141
+    ports: # ローカルブラウザからアクセスしない場合はポートを開放しなくても良い
+      - "4444:4444"

Seleniumの初期化時にリモートブラウザ(今回用意したコンテナ内部にあるChrome)に接続するように設定する。

application_system_test_case.rb
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  driven_by :selenium, using: :headless_chrome, screen_size: [1400, 1400], options: {
+    browser: :remote,
+    url: ENV.fetch("SELENIUM_DRIVER_URL"),
  }
end

いざテストを実行。

rails test test/system/welcomes_test.rb 

Capybara starting Puma...
* Version 5.5.2 , codename: Zawgyi
* Min threads: 0, max threads: 4
* Listening on http://127.0.0.1:40735
[Screenshot Image]: /app/tmp/screenshots/failures_test_-_ページを表示.png
E

Error:
WelcomesTest#test_/_ページを表示:
Selenium::WebDriver::Error::UnknownError: unknown error: net::ERR_CONNECTION_REFUSED
  (Session info: headless chrome=94.0.4606.61)
    test/system/welcomes_test.rb:6:in `block in <class:WelcomesTest>'

違うエラー。。。(次項へ続く)

システムテストを実行しようとしたら Selenium::WebDriver::Error::UnknownError: unknown error: net::ERR_CONNECTION_REFUSED エラー

原因

SeleniumのログをDEBUGレベルに落としてログを確認したところ、ChromeからRailsアプリケーションに接続できていないようであった。

Selenium::WebDriver.logger.level = Logger::DEBUG

先程のテスト実行時のログでは、Puma (アプリケーションサーバー)が127.0.0.1 (つまりRails用コンテナのlocalhost)をlistenしているので、おかしいと気づいた。

Capybara starting Puma...
* Version 5.5.2 , codename: Zawgyi
* Min threads: 0, max threads: 4
* Listening on http://127.0.0.1:40735

対策

Capybaraにserver_hostというオプションがあり、Capybaraが接続するアプリケーションサーバーのIPアドレスを指定できる。デフォルトのままでは127.0.0.1なので、Chrome用コンテナ内部に接続しようとしてエラーになっていた。(Railsアプリケーションは別のコンテナにある)

# - **server_host** (String = "127.0.0.1") - The IP address Capybara will bind the application server to. If the test application is to be accessed from an external host, you will want to change this to "0.0.0.0" or to a more specific IP address that your test client can reach.
capybara/capybara.rb at master · teamcapybara/capybara

よって、これをRails用コンテナのIPアドレスに指定すればOK。

application_system_test_case.rb
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  driven_by :selenium, using: :headless_chrome, screen_size: [1400, 1400], options: {
    browser: :remote,
    url: ENV.fetch("SELENIUM_DRIVER_URL"),
  }

+  Capybara.server_host = IPSocket.getaddress(Socket.gethostname)
end

これでテストを実行すると、

# rails test test/system/welcomes_test.rb 
Capybara starting Puma...
* Version 5.5.2 , codename: Zawgyi
* Min threads: 0, max threads: 4
* Listening on http://172.25.0.4:40833
.

Finished in 2.395570s, 0.4174 runs/s, 0.4174 assertions/s.
1 runs, 1 assertions, 0 failures, 0 errors, 0 skips

成功!!(Pumaがlistenしているホストも変更されている。)

Yuma ItoYuma Ito

開発時に便利なツール

Solargraph

コード補完などをしてくれる。

Gemfile
group :development do
  # for intellisense
  gem 'solargraph'
end

VSCodeの拡張機能は以下。
Ruby Solargraph - Visual Studio Marketplace

bundlerを使っている場合は、設定ファイルに以下を追加。

settings.json
{
    "solargraph.useBundler": true
}

RuboCop

Liner, Formatterツール。

Gemfile
group :development do
  # for lint
  gem 'rubocop', require: false
  gem 'rubocop-rails', require: false
end

VSCodeでは以下のように設定。

settings.json
{
    "ruby.lint": {
        "rubocop": true
    },
    "ruby.format": "rubocop"
}

自動フォーマットを実行するコマンド

rubocop -a
Yuma ItoYuma Ito

gemがどこにインストールされているのか確認したいとき

下記コマンドで出力される INSTALLATION DIRECTORYを確認すれば良い。

gem environment