行動すれば次の現実

ほどよくモダンなシステム開発を目指しています。メインテーマは生産性、Ruby、Javascriptです。

Rails6+Webpacker+Postgresql+SidekiqでDockerの開発環境を構築する手順

すでに稼働しているRailsアプリをDocker化する手順を紹介します。

Dockerの公式ガイドだけでは手順が不足している部分があったので、細かな部分も含め解説を加えています。

環境構成

バックエンド
- Rails 6.0
- Ruby 2.7.4
フロントエンド
- Webpacker 5.4.3
- NodeJS 12.22.0
DB
- PostgreSQL 11.14
Worker
- Sidekiq
- Redis

Dockerfile

webサーバー(ruby)の起動をDockerfileで定義します。 コメントで各コマンドの内容を説明しています。

# FROMにはnodeとrubyのイメージを指定します
# FROMはDockerfileの最初に記載する必要があり、以降の命令はFROMで指定したイメージに対して行われます
# このDockerfileではrubyのイメージに対して命令を記載しているため、FROMの定義順番もnode、rubyである必要があります。
# nodeイメージに対してはASでエイリアスを付けます。エイリアスはCOPYコマンド等で使用することが可能となります
FROM node:12.22.0 as node
FROM ruby:2.7.4

# タイムゾーンに東京を指定します
ENV TZ Asia/Tokyo

# db:migrateなどで使用されるpostgresql-clientをイメージ上にインストールします
RUN apt-get update -qq && apt-get install -y postgresql-client

# WORKDIRでアプリをインストールするディレクトリを指定します。
# 以降の命令は、移動せずともそのディレクトリに対して実行することが出来ます
# ディレクトリが存在しない場合は作成されます
WORKDIR /your-appname

# ホスト上にあるGemfileとGemfile.lockをイメージのWORKDIRに転送します
COPY Gemfile Gemfile
COPY Gemfile.lock Gemfile.lock

# イメージ上でbundle installを実行します
RUN bundle install

# entrypoint.shをイメージ上の/usr/bin/に配置します
# ENTRYPOINTで指定したコマンドはコンテナ稼働に必ず実行されます
# entrypoint.shはRailsアプリ特有の問題を対処します
# サーバー内にserver.pidというファイルが先に存在していたときに、サーバーが再起動できなくなる問題を回避するために使用されます
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]

# nodeイメージ上にインストールされているnode関連モジュールを、rubyイメージでも実行できるように転送します
# nodejsとnpmの各種コマンドが実行できるようにシンボリックリンクを貼ります
COPY --from=node /usr/local/bin/node /usr/local/bin/node
COPY --from=node /usr/local/include/node /usr/local/include/node
COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules
RUN ln -s /usr/local/bin/node /usr/local/bin/nodejs && \
    ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm

# ホスト上にあるpackage.json yarn.lock .postcssrc.ymlをWORKDIRに転送します
COPY package.json yarn.lock .postcssrc.yml ./

# npmでyarnモジュールをイメージ上にインストールして、yarn installを実行します
RUN npm install --global yarn
RUN yarn install

# ホストのモジュールを全てWORKDIRに転送します
# .dockerignoreに記載されたモジュールは転送されません(node_modulesなど)
COPY . ./

# ポート番号3000で公開します
EXPOSE 3000

# コンテナ稼働時に以下のrailsコマンドが実行されます
CMD ["rails", "server", "-b", "0.0.0.0"]

docker-compose.yml

web、db、webpacker、worker、redisのコンテナを定義します。 コメントで各コマンドの内容を説明しています。

version: "3.9"
services:
  web:
    # Dockerfileを使ってbuildします
    build: .
    # コンテナ稼働時に実行されるコマンドです
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    # 擬似端末(キーボードによる入力)をコンテナに結びつけます(docker run -itの-tと同じ意味)
    tty: true
    # 標準入出力とエラー出力をコンテナに結びつけます(docker run -itの-iと同じ意味)
    stdin_open: true
    # ホスト側とコンテナ側のポートを3000でマッピングしています
    ports:
      - "3000:3000"
    environment:
      # WEBPACKER_DEV_SERVER_HOSTでdev-serverの接続先を指定しています
      WEBPACKER_DEV_SERVER_HOST: webpacker
    volumes:
      # ホスト上のカレントディレクトリ(.)の内容をコンテナ上の/your-appnameに割り当てます(バインドマウント)
      - .:/your-appname:delegated
      # コンテナ上のgemインストール先(/usr/local/bundle)をbundleという名前でボリュームに割り当てます
      - bundle:/usr/local/bundle
    depends_on:
      # dbコンテナが起動してからwebを起動します
      - db
  db:
    # postgresのイメージを使用します
    image: postgres:11.14
    volumes:
      # コンテナ上のデータ保存先(/var/lib/postgresql/data)をpostgresという名前でボリュームに割り当てます
      - postgres:/var/lib/postgresql/data
    # ホスト側とコンテナ側のポートを5432でマッピングしています
    ports:
      - "5432:5432"
    environment:
      # Postgreのユーザー名、パスワード、DB名を指定します
      POSTGRES_USER: app_dp
      POSTGRES_PASSWORD: password
      POSTGRES_DB: your_appname_dev
      # タイムゾーンに東京を指定します
      TZ: "Asia/Tokyo"
      # エンコーディングとlocaleを指定します
      POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --locale=C"
  webpacker:
    # Dockerfileを使ってbuildします
    build: .
    # コンテナ稼働時に実行されるコマンドです
    command: bash -c 'rm -rf public/packs/* || true && bin/webpack-dev-server'
    volumes:
      # ホスト上のカレントディレクトリ(.)の内容をコンテナ上の/your-appnameに割り当てます(バインドマウント)
      - .:/your-appname
      # コンテナ上のnode_modulesを使用するためボリュームを割り当てます
      - node_modules:/your-appname/node_modules
    ports:
      # ホスト側とコンテナ側のポートを3035でマッピングしています
      - '3035:3035'
    environment:
      # IPアドレスに0.0.0.0を指定します
      WEBPACKER_DEV_SERVER_HOST: 0.0.0.0
  worker:
    # Dockerfileを使ってbuildします
    build: .
    # コンテナ稼働時に実行されるコマンドです
    command: bundle exec sidekiq -C config/sidekiq.yml
    volumes:
      # ホスト上のカレントディレクトリ(.)の内容をコンテナ上の/your-appnameに割り当てます(バインドマウント)
      - .:/your-appname
      # webコンテナと同じ名前のbundleボリュームを作成してgemを共有できるようにします
      - bundle:/usr/local/bundle
    environment:
      # redisのURLを指定します
      REDIS_URL: redis://redis:6379
    depends_on:
      # redisコンテナが起動してからwebを起動します
      - redis
  redis:
    # redisのイメージを使用します
    image: redis
    # コンテナ稼働時に実行されるコマンドです
    command: redis-server --appendonly yes
    ports:
      # ホスト側とコンテナ側のポートを6379でマッピングしています
      - "6379:6379"
    volumes:
      # コンテナ上のデータ保存先(/var/lib/redis/data)をredisという名前でボリュームに割り当てます
      - redis:/var/lib/redis/data
# 名前付きボリュームを定義します
volumes:
  bundle:
  postgres:
  redis:
  node_modules:

.dockerignore

dockerをビルドする際に必要のないファイルを.dockerignoreに記載します。 記載することでCOPYコマンドやADDコマンドでの転送時に対象外となります。

vendorやnode_modules等のコンテナで管理したいファイル群、転送の必要がないファイル群を除外しています。

/.bundle
/db/*.sqlite3
/db/*.sqlite3-journal
/log/*
/tmp/*
/storage/*
/node_modules
/yarn-error.log
/public/assets
/.byebug_history
/config/master.key
/vendor/*
/.idea/*
/config/master-staging.key
/public/packs
/public/packs-test
/node_modules
/yarn-debug.log*
/.yarn-integrity

entrypoint.sh

entrypoint.shはRailsアプリ特有の問題を対処します。 サーバー内にserver.pidというファイルが先に存在していたときに、サーバーが再起動できなくなる問題を回避するために使用されます。

#!/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 "$@"

config/database.yml

development:
  <<: *default
  # docker-composeで割り当てられたhostname(db)に変更します
  host: db
  # ・・・(以下省略)

config/initializers/sidekiq.rb

Sidekiq.configure_server do |config|
  if Rails.env.production?
    if ENV['REDIS_URL']
      config.redis = {url: ENV['REDIS_URL'], namespace: "dp_sidekiq_#{Rails.env}"}
    end
  else
    # docker-composeで割り当てられたhostname(redis)に変更します
    # config.redis = {url: 'redis://localhost:6379', namespace: "dp_sidekiq_#{Rails.env}"}
    config.redis = {url: 'redis://redis:6379', namespace: "dp_sidekiq_#{Rails.env}"}
  end
end

Sidekiq.configure_client do |config|
  if Rails.env.production?
    if ENV['REDIS_URL']
      config.redis = {url: ENV['REDIS_URL'], namespace: "dp_sidekiq_#{Rails.env}"}
    end
  else
    # docker-composeで割り当てられたhostname(redis)に変更します
    # config.redis = {url: 'redis://localhost:6379', namespace: "dp_sidekiq_#{Rails.env}"}
    config.redis = {url: 'redis://redis:6379', namespace: "dp_sidekiq_#{Rails.env}"}
  end
end

起動確認

1. イメージを作成する

docker-compose build --no-cache

2. イメージを元にコンテナを作成し、起動する

docker-compose up -d

3. migrationを流す

docker-compose run web rails db:migrate

4. アプリにアクセスする

http://localhost:3000/

よく使うdocker & docker-composeコマンド

最後によく使用するコマンドをまとめました。

# イメージを作成する
docker-compose build --no-cache

# コンテナを作成して起動する
docker-compose up

# コンテナを停止する
control + c

# コンテナの起動(バックグラウンド)
docker-compose up -d

# コンテナを停止する(バックグラウンド)
docker-compose stop

# コンテナを停止して削除する
docker-compose down

# webコンテナにログインする
docker exec -it your-appname_web_1 bash

# コマンドを実行する
docker-compose run web rails db:migrate

# イメージの一覧を出力する
docker images

# コンテナの一覧を出力する
docker ps -a