🦔

rails7をdockerで起動させるテンプレート作成してみた

2023/02/04に公開

今回作るものRails7をdockerで環境構築してみた

今回は環境に依存しないようにするためにcloud9で作ってみました。
PC環境でも動きます!
rails 7系
ruby 3.1.1
mysql 8.0.29

githubリンク
https://github.com/hirosi1900day/rails7-docker/tree/master/rails_app

rails_app/Dockerfile

# dockerhubからイメージ取得
FROM ruby:3.1.1

# railsに必要な依存ライブラリー追加(build-essential libpq-dev nodejs)
# 参考: https://docs.docker.jp/compose/rails.html
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs

# APP_PATHという変数をセット
ENV APP_PATH /myapp

# /myappというディレクトリー作成
RUN mkdir $APP_PATH

# /myappというディレクトリー移動
WORKDIR $APP_PATH

# ローカルからGemfileをコンテナの/myappのGemfileにコピー
COPY Gemfile $APP_PATH/Gemfile

# ローカルからGemfile.lockをコンテナの/myappのGemfile.lockにコピー
COPY Gemfile.lock $APP_PATH/Gemfile.lock

# Gemfile, Gemfile/lockを元にgemをインストール
RUN bundle install

# ローカルの全てのrailsファイルをコンテナ側にコピー(レイヤーキャッシュを聞かせるためにbundle installの後に実行する必要がある)
# bundle installは時間がかかるコマンドなので下記をbundle installより前に持ってくると例えばapp/viewsを変更するたびにキャッシュが使えなくなるのでbundle installが走ってしまう
COPY . $APP_PATH

EXPOSE 3000

USER root

rails_app/docker-compose.yml

version: "3"
services:
  db:
      # dockerhubからmysqlイメージ取得
    image: mysql:8.0.29
    
    # 起動時に常にスタートするように設定
    restart: always
    
    # 環境変数にMYSQL_ROOT_PASSWORDをセットしrootユーザーのパスワードを指定してDBコンテナを起動させる(他にも色々環境変数があるのでhttps://hub.docker.com/_/mysql)
    environment:
      MYSQL_ROOT_PASSWORD: password
      
    # mysql 8.0から認証方法が変わったので以前の認証方法で起動させるようにし、railsのエラーを防ぐ
    command: --default-authentication-plugin=mysql_native_password
    
    # mysql-dataとコンテナの/var/lib/mysqlをリンクさせ、データの永続化する
    volumes:
      - mysql-data:/var/lib/mysql
      
    # ローカルの3306ポートとコンテナの3306ポートを結びつける
    ports:
      - 3306:3306
      
    # 使用する環境を指定
    platform: linux/x86_64/v8
  rails:
    build:
      # Dockerfileのある位置を指定
      context: .
      dockerfile: Dockerfile
      
     # 起動時に行うコマンドを指定
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"  
    
    # ローカルの全てのファイルとコンテナのmyappを紐付けることで、vscodeでの編集内容を常時コンテナに転送している
    volumes:
      - .:/myapp
      
    # 環境変数を指定する。ほぼDBへの接続設定時に使用される
    environment:
      TZ: Asia/Tokyo
      RAILS_ENV: development
      DB_HOST: db
      DB_USER: root
      MYSQL_ROOT_PASSWORD: password
      
    # ローカルの80ポートとコンテナの3000ポートを紐付けブラウザーの80ポートで接続できるようにしている
    ports:
      - "80:3000"
      
    # 正常終了してしまうとコンテナに入れないので, 正常終了しないようにしている(正確には仮想端末プロセスを立ち上げることでキーボードなどからのシグナルを受け付けるプロセスが起動するため親プロセスが停止しなくなる docker run -it <container_name>の-tにあたる設定)
    tty:
      true
    # stdin_openとは標準入出力とエラー出力をコンテナに結びつける設定 docker run -it <container_name>の-iにあたる設定
    stdin_open: true
    depends_on:
      - db
# host(pc)に保存領域作成
volumes:
  mysql-data:

config/database.ymlの下記にように変更

default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: <%= ENV['DB_USER'] %>
  password: <%= ENV['MYSQL_ROOT_PASSWORD'] %>
  host: <%= ENV['DB_HOST'] %>

cloud9の場合はホストブロックがかかるから
config/environment/development.rbに下記を追加してね!

config.hosts.clear

rails-nginx構成で作成するには

こちらのリポジトリーを参考にしてください
https://github.com/hirosi1900day/rails-nginx-docker-dev

こちらのリポジリーではnginxコンテナを追加し、railsとの連携をしています。

DB初期化のためのshell script作成

entrypoint.sh

#!/bin/bash
set -e

bundle exec rails db:create
bundle exec rails db:migrate
bundle exec rails db:seed

exec "$@"
rails Dockerfile修正
FROM ruby:3.1.1

RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs

ENV APP_PATH /myapp

RUN mkdir $APP_PATH
WORKDIR $APP_PATH

COPY Gemfile $APP_PATH/Gemfile
COPY Gemfile.lock $APP_PATH/Gemfile.lock
RUN bundle install

COPY . $APP_PATH

EXPOSE 3000

USER root
# ここから追加
VOLUME /myapp/public <= nginxとファイル共有用のvolume作成
VOLUME /myapp/tmp  <= nginxとファイル共有用のvolume作成
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
CMD /bin/bash -c "rm -f tmp/pids/server.pid && bundle exec puma -C config/puma.rb"
nginxコンテナ

containers/nginx/Dockerfile

FROM nginx:1.15.8

# インクルード用のディレクトリ内を削除
RUN rm -f /etc/nginx/conf.d/*

# Nginxの設定ファイルをコンテナにコピー
ADD nginx.conf /etc/nginx/conf.d/webapp.conf

# ビルド完了後にNginxを起動
CMD /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf
docker-compose.ymlにnginxコンテナを追加
version: "3"
services:
  db:
    image: mysql:8.0.29
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: password
    command: --default-authentication-plugin=mysql_native_password
    volumes:
      - mysql-data:/var/lib/mysql
    ports:
      - 3306:3306
    platform: linux/x86_64/v8
  rails:
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - .:/myapp
      - public-data:/myapp/public  <= nginxにデータを送るためにhost volumeにファイル共有することで共有
      - tmp-data:/myapp/tmp <= railsのソケットを共有するために/myapp/tmpもhost volumeにファイル共有
    environment:
      TZ: Asia/Tokyo
      RAILS_ENV: development
      DB_HOST: db
      DB_USER: root
      MYSQL_ROOT_PASSWORD: password
    ports:
      - "3000:3000"
    tty:
      true
    stdin_open: true
    depends_on:
      - db
  web:  <= ここ追加
    build:
      context: containers/nginx
    volumes:
      - public-data:/myapp/public <= myapp/publicにrails コンテナのmyapp/publicを共有
      - tmp-data:/myapp/tmp <= myapp/tmpにrails コンテナのmyapp/tmpを共有。ソケット共有のため
    ports:
      - 80:80
    depends_on:
      - rails
volumes:
  mysql-data: 
  public-data: <= railsとnginxコンテナの情報を共有するために作成
  tmp-data: <= railsのソケットをnginxコンテナに共有するために作成
pumaの設定変更
# Puma can serve each request in a thread from an internal thread pool.
# The `threads` method setting takes two numbers: a minimum and maximum.
# Any libraries that use thread pools should be configured to match
# the maximum value specified for Puma. Default is set to 5 threads for minimum
# and maximum; this matches the default thread size of Active Record.
#
max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
threads min_threads_count, max_threads_count

# Specifies the `worker_timeout` threshold that Puma will use to wait before
# terminating a worker in development environments.
#
worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development"

# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
#
# port ENV.fetch("PORT") { 3000 }

# Specifies the `environment` that Puma will run in.
#
environment ENV.fetch("RAILS_ENV") { "development" }

# Specifies the `pidfile` that Puma will use.
pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }

# Specifies the number of `workers` to boot in clustered mode.
# Workers are forked web server processes. If using threads and workers together
# the concurrency of the application would be max `threads` * `workers`.
# Workers do not work on JRuby or Windows (both of which do not support
# processes).
#
# workers ENV.fetch("WEB_CONCURRENCY") { 2 }

# Use the `preload_app!` method when specifying a `workers` number.
# This directive tells Puma to first boot the application and load code
# before forking the application. This takes advantage of Copy On Write
# process behavior so workers use less memory.
#
# preload_app!

# Allow puma to be restarted by `rails restart` command.
plugin :tmp_restart

app_root = File.expand_path("..", __dir__)
# nginx.confのserverと一致させる。
bind "unix://#{app_root}/tmp/sockets/puma.sock"

stdout_redirect "#{app_root}/log/puma.stdout.log", "#{app_root}/log/puma.stderr.log", true
nginxの設定

containers/nginx/nginx.conf

# プロキシ先の指定
# Nginxが受け取ったリクエストをバックエンドのpumaに送信
upstream myapp {
  # ソケット通信したいのでpuma.sockを指定volumeでrailsコンテナとnginxコンテナを連携しているので通信ができる
  server unix:///myapp/tmp/sockets/puma.sock;
}

server {
  listen 80;
  # ドメインもしくはIPを指定
  server_name localhost;

  access_log /var/log/nginx/access.log;
  error_log  /var/log/nginx/error.log;

  # ドキュメントルートの指定
  root /myapp/public; <= volumeでnginxコンテナとrailsコンテナの/myapp/publicを共有しているので指定できる

  client_max_body_size 100m;
  error_page 404             /404.html;
  error_page 505 502 503 504 /500.html;
  try_files  $uri/index.html $uri @myapp;
  keepalive_timeout 5;

  # リバースプロキシ関連の設定
  location @myapp {
    proxy_set_header X-Real-IP $remote_addr; <= $remote_addrはアクセス元のIP。ネットワーク層の情報。基本的に直前のIPを保持している、X-Real-IPロードバランサやプロキシを経由する時に送信元を判別するために利用。アプリケーション層の情報。
X-Forwarded-Forと同じような値だけど、複数の可能性があるX-Forwarded-Forと違って1つ。
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; <= X-Forwarded-ForはHTTPヘッダの一つ。ロードバランサやプロキシを経由する時に送信元を判別するために利用
    proxy_set_header Host $http_host; <= HostはHost!
    proxy_pass http://myapp; <= これで上に定義しているupstream myapp に処理が移動する
  }
}
  • X-Forwarded-Forについて
    Client
    ↓ X-Forwarded-For: ""
    Proxy1
    ↓ X-Forwarded-For: "Client"
    Proxy2
    ↓ X-Forwarded-For: "Client, Proxy1"
    ELB
    ↓ X-Forwarded-For: "Client, Proxy1, Proxy2"
    Nginx
    HTTPヘッダの一つ。ロードバランサやプロキシを経由する時に送信元を判別するために利用。アプリケーション層の情報。という順に追加されていく。なので送信元を知りたければ1つ目の要素を見れば良い

Discussion