LoginSignup
67
8

【Ruby on Rails】Railsをdocker-composeで動かすときのミニマムな構成

Last updated at Posted at 2023-12-01

まえがき

本記事は、DMM WEBCAMP Advent Calendar 2023 2日目記事です。

Ruby on Railsを中心に、DWCメンター・卒業生が記事を投稿しておりますので、是非他の記事もご確認ください!

はじめに

こんにちは、DMM WEBCAMP メンターの @ukwhatn です。

私は業務/プライベートの双方でRailsを書いているのですが、
特にRails7について、Docker(docker compose)で動かすための構成を調べてもあまり情報がなく、頭を抱えたことが何度かあります。

テンプレートリポジトリとしてRyan Williamsさんのrails7-on-dockerなどはありますが、全部盛りすぎて少々使いにくい部分も....

そこで今回は、私が個人的に構築したRails7をDockerで動かすためのテンプレートリポジトリを紹介しつつ、その内容を解説します。

結論

作ったのが↓です。

本記事では、このテンプレートリポジトリから重要な部分をかいつまんで解説します。

解説

Docker関連

まずは「Dockerで動かす」ための部分から見ていきます

compose.yml

コード
services:
  web:
    build:
      context: .
      dockerfile: ./deployment/dockerfiles/web/Dockerfile
      args:
        RUBY_VERSION: 3.2.2
    image: web-dev
    entrypoint: [ "bin/docker-entrypoint.sh" ]
    command: bash -c "rm -f tmp/pids/server.pid && bin/rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/usr/src/app
      - bundle:/usr/local/bundle
    ports:
      - "127.0.0.1:59998:3000"
    environment:
      - HISTFILE=/usr/src/app/log/.bash_history
      - RAILS_ENV=development
      - DB_HOST=${DEV_DB_HOST}
      - DB_PORT=${DEV_DB_PORT}
      - DB_USER=${DEV_DB_USER}
      - DB_PASSWORD=${DEV_DB_PASSWORD}
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy

  db:
    image: postgres:15.4
    environment:
      - POSTGRES_USER=${DEV_DB_USER}
      - POSTGRES_PASSWORD=${DEV_DB_PASSWORD}
    volumes:
      - pg_data:/var/lib/postgresql/data
    restart: always
    healthcheck:
      test: pg_isready -U postgres
      interval: 2s
      timeout: 5s
      retries: 30

  redis:
    image: redis:7.2.0
    volumes:
      - redis_data:/data
    healthcheck:
      test: redis-cli ping
      interval: 2s
      timeout: 5s
      retries: 30

volumes:
  pg_data:
  redis_data:
  bundle:

↑は正確に言えばcompose.dev.ymlの中身です。
他にcompose.stg.ymlcompose.prod.ymlがありますが、環境変数名のprefixが違うだけなので割愛します。

このcompose.ymlでは、以下のコンテナが動作します。

  • web
    • railsアプリ本体が動作するコンテナです
      • Dockerfileは後述
    • ポイント
      • build引数としてRUBY_VERSIONを定義し、Dockerfileから参照することでベースイメージのバージョンを切り替えられるようにしています
      • environmentでRAILS_ENVやDB接続情報を渡しており、これを切り替えることでdev/stg/prodを分けています
      • depends_onでpostgresやredisの立ち上がり(service_healthy)を待ってからwebコンテナを上げています
  • db
    • みんな大好きpostgresのオフィシャルイメージです
    • ポイント
      • environmentで渡しているPOSTGRES_USERPOSTGRES_PASSWORDはrailsに渡しているものと共通です
      • pg_isready -U postgresでhealthcheckを行うことで、service_healthyコンディションによるdepends_onを有効化しています
  • redis
    • セッションストアとして利用するためのredisコンテナです
    • ポイント
      • redis-cli pingでhealthcheckを行うことで、service_healthyコンディションによるdepends_onを有効化しています

Dockerfile(web)

deployment/dockerfiles/web/Dockerfile にあります

コード
ARG RUBY_VERSION

FROM ruby:${RUBY_VERSION}-slim

# OS Level Dependencies
RUN --mount=type=cache,target=/var/cache/apt \
  --mount=type=cache,target=/var/lib/apt,sharing=locked \
  --mount=type=tmpfs,target=/var/log \
  rm -f /etc/apt/apt.conf.d/docker-clean; \
  echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache; \
  apt-get update -qq \
  && apt-get install -yq --no-install-recommends \
    build-essential \
    gnupg2 \
    less \
    git \
    libpq-dev \
    postgresql-client \
    libvips \
    curl

ENV LANG=C.UTF-8 \
  BUNDLE_JOBS=4 \
  BUNDLE_RETRY=3

RUN gem update --system && gem install bundler

WORKDIR /usr/src/app

ENTRYPOINT ["./bin/docker-entrypoint.sh"]

EXPOSE 3000

あんまり複雑なことはしてないです。

DBをmysqlとかに変えたい場合はここでlibpq-devpostgresql-clientのかわりに適切なものをinstallしてください。

docker-entrypoint.sh

bin/docker-entrypoint.shにあります。
compose.ymlのentrypointに設定しているものです。

コード
#!/bin/bash
set -e

echo -e "----------------------\nInitializing Rails App\nMODE: $RAILS_ENV\n----------------------\n"

# Remove a potentially pre-existing server.pid for Rails.
rm -f /usr/src/app/tmp/pids/server.pid

echo "rails: installing gems..."
bundle check || bundle install --jobs 4

echo "rails: creating database..."
bundle exec rake db:create
echo "rails: migrating database..."
bundle exec rake db:migrate

# productionモードの場合はassetsをコンパイルする
if [ "$RAILS_ENV" = 'production' ]; then
  echo "rails: precompiling assets..."
  bundle exec rake assets:precompile
fi

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"

やっていることは単純で、

  1. server.pidの削除
  2. bundle install
  3. dbのマイグレーション
  4. アセットプリコンパイル(prodモード時のみ)

だけです。
コンテナ作成時にやらせたい処理があったらここに追記しましょう。


Docker周りで大事なのはこのくらいです
総じてあまり複雑なことはしていないので、必要に応じて書き換えつつ利用してください。

Rails関連

ここからは、中で動くRailsの設定ファイル等を見ていきます。

Gemfile

コード
# frozen_string_literal: true

source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby "3.2.2"

# Rails 7
gem "rails", "~> 7.0.7", ">= 7.0.7.2"

# For asset pipeline
gem "sprockets-rails"

# Postgresql driver
gem "pg", "~> 1.1"

# Web server
gem "puma", "~> 6.3"

# Use import maps
gem "importmap-rails"

# Use Turbo
gem "turbo-rails"

# Stimulus Framework
gem "stimulus-rails"

# JSON Builder
gem "jbuilder"

# Redis
gem "redis", "~> 4.8.1", "< 5"
gem "redis-rails"

# Create password hash
# gem "bcrypt", "~> 3.1.7"

# Timezone for Windows
gem "tzinfo-data", platforms: %i[mingw mswin x64_mingw jruby]

# Reduces boot times through caching
gem "bootsnap", require: false

# Use Sass to process CSS
gem "dartsass-rails"
gem "sassc-rails"

# Use Active Storage variants
gem "image_processing", "~> 1.2"

# for localize
gem "i18n_generators"
gem "rails-i18n"

# for read .env
# gem "dotenv-rails"

# for discord oauth2
# gem "omniauth-discord"

# for google oauth2
# gem "omniauth-google-oauth2"

# for github oauth2
# gem "omniauth-github"

# csrf protection for omniauth
# gem "omniauth-rails_csrf_protection"

group :development, :test do
  # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
  gem "brakeman"
  gem "bundler-audit"
  gem "debug", "1.8.0", platforms: %i[mri mingw x64_mingw]
  gem "rspec-rails"
  gem "rubocop"
  gem "rubocop-performance"
  gem "rubocop-rails"
  gem "rubocop-rspec"
end

group :development do
  # Use console on exceptions pages [https://github.com/rails/web-console]
  gem "web-console"

  # Add speed badges [https://github.com/MiniProfiler/rack-mini-profiler]
  gem "rack-mini-profiler"

  # Speed up commands on slow machines / big apps [https://github.com/rails/spring]
  # gem "spring"
end

group :test do
  # Use system testing [https://guides.rubyonrails.org/testing.html#system-testing]
  gem "capybara"
  gem "selenium-webdriver"
  gem "webdrivers"
end

ここもテンプレートから逸脱したことはあまりしていません。
OAuth2を使いたい場合はomniauth系のGemのコメントアウトを外してください。

.rubocop.yml

コード
require:
  - rubocop-rails
  - rubocop-rspec
  - rubocop-capybara
  - rubocop-performance

AllCops:
  TargetRubyVersion: 3.2.2
  TargetRailsVersion: 7.0.1
  DisabledByDefault: true
  DisplayCopNames: true
  NewCops: enable
  Exclude:
    - "bin/**/*"
    - "vendor/**/*"
    - "public/**/*"
    - "node_modules/**/*"
    - "db/schema.rb"

# ----- bundler -----
Bundler:
  Enabled: true

# ----- layout -----

Layout:
  Enabled: true

# ----- lint -----

Lint:
  Enabled: true

Lint/EmptyBlock:
  Enabled: false
  Exclude:
    - "config/routes.rb"

# ----- naming -----

Naming:
  Enabled: true

# ----- security -----

Security:
  Enabled: true

# ----- performance -----

Performance:
  Enabled: true

# ----- style -----

# ブロックの終了部分が適切にスタイル指定されているか
Style/EndBlock:
  Enabled: true
# ハッシュの構文が適切か
Style/HashSyntax:
  Enabled: true
# 文字列リテラルが適切にスタイル指定されているか
Style/StringLiterals:
  Enabled: true
  EnforcedStyle: "double_quotes"
  Exclude:
    - "config/**/*.rb"
    - Rakefile
# caseの等価性が適切にチェックされているか
Style/CaseEquality:
  Enabled: true
# クラスメソッドが適切に定義されているか
Style/ClassMethods:
  Enabled: true
# クラス変数が適切に使用されているか
Style/ClassVars:
  Enabled: true
# collectionメソッドが適切に使用されているか
Style/CollectionMethods:
  Enabled: true
  PreferredMethods:
    collect: "map"
    collect!: "map!"
    inject: "reduce"
    detect: "find"
    find_all: "select"

テンプレートリポジトリなので、rubocopは必要な部分だけtrueにしています。
特にStyle/*については、用途に応じて増やしたり減らしたりしてください。

session_store.rb

config/initializers/session_store.rbにあります

コード
secure = Rails.env.production?
key = Rails.env.production? ? "_app_session" : "_app_session_#{Rails.env}"
domain = ENV.fetch("APP_DOMAIN", "localhost")

Rails.application.config.session_store :redis_store,
                                       servers: %w(redis://redis:6379/0/session),
                                       expire_after: 90.minutes,
                                       key:,
                                       domain:,
                                       secure:,
                                       httponly: true,
                                       threadsafe: true
                                    

設定値は redis-railsの公式READMEを確認してください。
多分変える必要があるのはexpire_afterくらいだと思います。


Rails周りはこのくらいです。
必要に応じてカスタムしてください。

その他

Makefile

dockerコマンド打つのめんどくさいので、Makefileにまとめました

コード
.PHONY: test build-dev

DIR_NAME := $(shell basename $(CURDIR))

init:
	cp .env.example .env

dev:
	make build-dev
	make up-dev
	make log-dev

build-dev:
	docker compose -f compose.dev.yml build

up-dev:
	docker compose -f compose.dev.yml up -d

down-dev:
	docker compose -f compose.dev.yml down

clean-dev:
	docker compose -f compose.dev.yml down --rmi all --volumes --remove-orphans

log-dev:
	docker compose -f compose.dev.yml logs -f

prod:
	make build-prod
	make up-prod
	make log-prod

build-prod:
	docker compose -f compose.prod.yml build

up-prod:
	docker compose -f compose.prod.yml up -d

down-prod:
	docker compose -f compose.prod.yml down

log-prod:
	docker compose -f compose.prod.yml logs -f

redis-cli:
	docker compose -f compose.dev.yml exec redis redis-cli

.env.example

DEV/TEST/PRODで使い分けられるようにしています

コード
# -- DEVELOPMENT --
DEV_DB_HOST="db"
DEV_DB_PORT="5432"
DEV_DB_USER="postgres"
DEV_DB_PASSWORD="changeme"

# -- STAGING --
STG_DB_HOST="db"
STG_DB_PORT="5432"
STG_DB_USER="postgres"
STG_DB_PASSWORD="changeme"

# -- TEST --
TEST_DB_HOST="db"
TEST_DB_PORT="5432"
TEST_DB_USER="postgres"
TEST_DB_PASSWORD="changeme"

# -- PRODUCTION --
PROD_DB_HOST="db"
PROD_DB_PORT="5432"
PROD_DB_USER="postgres"
PROD_DB_PASSWORD="changeme"

dependabot.yml

.github/depandabot.ymlにあります

コード
version: 2
updates:
- package-ecosystem: docker
  directory: "/"
  schedule:
    interval: daily
- package-ecosystem: bundler
  directory: "/"
  schedule:
    interval: daily
  ignore:
    - dependency-name: "rails"
      update-types: ["version-update:semver-major", "version-update:semver-minor"]
- package-ecosystem: github-actions
  directory: "/"
  schedule:
    interval: daily

Gemfileを読みに行って、アップデートがあればPRを作ってくれます。
便利なので有効化しておきましょう。


以上です。

割と雑に作ったテンプレートなので、気になる部分があればコメントやPRでご指摘ください。

アドベントカレンダーは明日からも投稿が続きますので、お楽しみに!

67
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
67
8