自己学習のためにざっくり作ってみた
個人的には、本番はUnicornでも開発環境ではPumaでいいんじゃないかな...って思っている
SQLは合わせたほうが良いと思うけど
前準備
experimentalな機能を使うので環境変数を予め設定しておく
.envrc
export DOCKER_BUILDKIT=1
export COMPOSE_DOCKER_CLI_BUILD=1
Dockerfile
# syntax = docker/dockerfile:1.0-experimental
################################################################
# node Image
################################################################
# 先にマルチステージビルドでNode.jsとYarnを用意
# そうすることで、rails側のDockerfileの記述がシンプルになる
FROM node:12.18-alpine as node
# zipダウンロードの流派もあるけど、記述がシンプルなので apk add を使用
RUN apk --update add --no-cache yarn
################################################################
# rails Image
################################################################
FROM ruby:2.7.1-alpine
# node image から、Node.jsとYarnをコピー
COPY --from=node /usr/local/bin/node /usr/local/bin/node
COPY --from=node /opt/yarn-* /opt/yarn
RUN ln -fs /opt/yarn/bin/yarn /usr/local/bin/yarn
ENV RAILS_ENV development
ENV NODE_ENV development
ENV ROOT_PATH /app/
ENV LANG C.UTF-8
ENV PORT 80
WORKDIR $ROOT_PATH
# 必要なパッケージのインストール
RUN apk add --update --no-cache \
postgresql-client \
xz-dev \
tzdata
# ビルドでしか使用しないパッケージは
# 後で削除するため virtual tag をつけている
RUN apk add --update --no-cache --virtual=build-dependencies \
build-base \
curl-dev \
linux-headers \
libxml2-dev \
libxslt-dev \
postgresql-dev \
ruby-dev \
yaml-dev \
zlib-dev
# Gemfile.lock とおなじバージョンの bundler をインストール
RUN gem install bundler -v '2.1.4'
# 全部コピーするとRailsアプリの変更の度にここからビルドし直しになるので
# 必要なGemfileやyarnのファイルを先にコピーする
COPY Gemfile Gemfile.lock package.json yarn.lock $ROOT_PATH
# mount cacheを利用して、gemをキャッシュする
RUN bundle config set path .cache/bundle
RUN --mount=type=cache,target=/app/.cache/bundle \
bundle install && \
mkdir -p vendor && \
cp -ar .cache/bundle vendor/bundle
# ビルドでのみ使用するパッケージの削除
RUN apk del --purge build-dependencies
COPY . /myapp
# 起動
CMD rm -f tmp/pids/unicorn.pid && \
bundle exec unicorn_rails -E $RAILS_ENV -c config/unicorn.rb -p ${PORT}
Dockerfileの方針
ビルド時間を短くして、無駄な時間を減らす
そのために、
- 上の方は変更がなるべく無いようにして、イメージレイヤーのビルド数をへらす
- alpineを利用したり、不要なパッケージを削除して、なるべくimageサイズを小さくする
- mount cacheを利用して、gemをキャッシュする(2分半ほど処理が短くなった!)
docker-compose.yml
version: '3'
services:
db:
image: postgres:12-alpine
volumes:
- db_data:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: 'postgres'
ports:
- "5432:5432"
web:
build: .
ports:
- "3000:80"
volumes:
- .:/app
environment:
DATABASE_USER: postgres
DATABASE_PASSWORD: postgres
DATABASE_HOST: db
depends_on:
- db
volumes:
db_data:
docker-compose.yml の方針
あんまり変わったことはしていない
Dockerfile側は、なるべく変数で制御して、環境毎の変数を docker-compose 側に持たせる流派もあるけど、まぁそこまではやらなくても良いのかな...? と思っている
- 個人的には、Dockerfileはそこまで汎化せずに、環境毎にDockerfile作ってもいいんじゃないかなと思っている
- だいたい事情で汎用化は破綻するので
以下に、Docker関係以外で初期状態から設定を追加/変更した部分を記す
config/unicorn.rb
APP_DIR = `pwd`.delete("\n")
working_directory APP_DIR
pid "#{APP_DIR}/tmp/pids/unicorn.pid"
worker_processes 2
listen '/tmp/unicorn.sock', backlog: 64
timeout 60
config/unicorn.rb の方針
あんまり特筆すべきことはない。 backlog
の数をデフォルトから減らしているところくらいかな...
config/database.yml
default: &default
adapter: postgresql
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
timeout: 5000
username: <%= ENV['DATABASE_USER'] %>
password: <%= ENV['DATABASE_PASSWORD'] %>
host: <%= ENV['DATABASE_HOST'] %>
port: <%= ENV['DATABASE_PORT'] %>
development:
<<: *default
database: development
test:
<<: *default
database: test
production:
<<: *default
database: production
config/database.yml の方針
こちらも、各設定値を環境変数から取ってくるように変えたくらいかな...
config/environments/development.rb
ログ出力の標準出力化
config.file_watcher = ActiveSupport::EventedFileUpdateChecker
config.logger = ActiveSupport::Logger.new($stdout)
$stdout.sync=true
参考
👇 buildkit を使ったDocker buildの解説
medium.com
👇 docker-compose で buildkit を使う方法の解説
qiita.com
👇 効率の良いDockerfileの書き方
www.slideshare.net
👇 jokerさんによる、最近のRailsのDockerfileの書き方
speakerdeck.com
👇 効率の良いRailsのDockerfileの書き方
techracho.bpsinc.jp