まえがき
本記事は、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.yml
とcompose.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コンテナを上げています
- railsアプリ本体が動作するコンテナです
-
db
- みんな大好きpostgresのオフィシャルイメージです
-
ポイント
- environmentで渡している
POSTGRES_USER
とPOSTGRES_PASSWORD
はrailsに渡しているものと共通です -
pg_isready -U postgres
でhealthcheckを行うことで、service_healthy
コンディションによるdepends_onを有効化しています
- environmentで渡している
-
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-dev
やpostgresql-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 "$@"
やっていることは単純で、
- server.pidの削除
- bundle install
- dbのマイグレーション
- アセットプリコンパイル(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でご指摘ください。
アドベントカレンダーは明日からも投稿が続きますので、お楽しみに!