自己学習のためにざっくり作ってみた
個人的には、本番は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' # 開発用。直接DBにアクセスしたい時のために ports: - "5432:5432" web: build: . ports: - "3000:80" volumes: - .:/app environment: DATABASE_USER: postgres DATABASE_PASSWORD: postgres DATABASE_HOST: db depends_on: - db # databaseのデータはvolumeとして保存 volumes: db_data:
docker-compose.yml の方針
あんまり変わったことはしていない
Dockerfile側は、なるべく変数で制御して、環境毎の変数を docker-compose 側に持たせる流派もあるけど、まぁそこまではやらなくても良いのかな...? と思っている
- 個人的には、Dockerfileはそこまで汎化せずに、環境毎にDockerfile作ってもいいんじゃないかなと思っている
- だいたい事情で汎用化は破綻するので
以下に、Docker関係以外で初期状態から設定を追加/変更した部分を記す
config/unicorn.rb
# frozen_string_literal: true APP_DIR = `pwd`.delete("\n") working_directory APP_DIR pid "#{APP_DIR}/tmp/pids/unicorn.pid" worker_processes 2 # backlog は、workerが作業中でも受け取るTCPコネクションの数 # デフォルトの1024だと大量アクセスの時に待ちが大量発生するので減らしている 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
ログ出力の標準出力化
# Use an evented file watcher to asynchronously detect changes in source code, # routes, locales, etc. This feature depends on the listen gem. config.file_watcher = ActiveSupport::EventedFileUpdateChecker config.logger = ActiveSupport::Logger.new($stdout) # syncを有効にしないと、バッファリングされてログが一定量溜まらないと出力されない $stdout.sync=true
参考
👇 buildkit を使ったDocker buildの解説
👇 docker-compose で buildkit を使う方法の解説
👇 効率の良いDockerfileの書き方
www.slideshare.net
👇 jokerさんによる、最近のRailsのDockerfileの書き方
👇 効率の良いRailsのDockerfileの書き方