0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ローカル環にて、Docker + Docker compose + Rails8 + Nginx + MySQL8の環境を構築

Posted at

前書き

ローカル環境にて、表題の通り環境を構築したので、その構築方法について記載します。

Rails 8 では、Thrusterと言うHTTP/2プロキシサーバーが、構築時から導入されている為、必ずしもNginx等のリバースプロシキを導入する必要はないです。それを、理解した上で今回は構築を行なっております。

用語説明

用語 説明
TCP インターネットでデータを確実に届ける為の通信プロトコル(規則)。YouTubeの動画視聴、Webページの閲覧、メールの送受信等、正確なデータ転送を行う際に使用する重要な仕組み。
HTTP/2 1つのTCP接続を使って複数のリクエストを同時に処理が出来る為、HTTP/1.1で発生していた「リクエストの順番待ち(ヘッドオブラインブロッキング)」の問題を解消。
更に、データ圧縮 や サーバープッシュ(サーバーからクライアントへ事前にデータを送る仕組み) により、Webページの表示速度が向上する。
Thruster Rails 8に導入された HTTP/2対応のリバースプロキシサーバー。従来Nginxが担っていたHTTP/2処理や負荷分散をRails環境に最適化。静的ファイルの配信やHTTPSの終端処理 (SSL/TLSの処理) も可能。
プロキシサーバー クライアント(ブラウザなど)とサーバーの間に入り、リクエストを中継するサーバー。通信の最適化やキャッシュ、セキュリティ向上を目的とする。フォワードプロキシリバースプロキシの2種類がある。
リバースプロキシ クライアントからのリクエストを受け取り、適切なバックエンドサーバー(Puma等)へ転送するプロキシ。負荷分散 (ロードバランシング) やHTTP/2対応、静的ファイルの配信を効率化出来る。
Nginx 高速なHTTPサーバー兼リバースプロキシ。静的ファイルの配信、HTTP/2対応、ロードバランシング、SSL/TLS終端処理等に優れる。RailsではPumaの前段に置いて、処理の最適化を行うのが一般的だったが、Rails 8Thrusterにより不要になるケースも増える。

構築手順

必要ファイルの作成

  • 任意のディレクトリを作成後、そのディレクトリー内で下記のコマンドを実行する
mkdir -p backend/web1/settings backend/nginx/conf backend/nginx/error_page db/conf phpmyadmin/conf && \
touch backend/web1/settings/.env && \
touch backend/web1/settings/entrypoint.sh && \
touch backend/web1/Dockerfile.dev && \
touch backend/web1/Gemfile && \
touch backend/web1/Gemfile.lock && \
touch backend/nginx/conf/web1.conf && \
touch backend/nginx/error_page/404.html && \
touch backend/nginx/error_page/422.html && \
touch backend/nginx/error_page/500.html && \
touch backend/nginx/Dockerfile && \
touch db/conf/.env && \
touch phpmyadmin/conf/.env && \
touch compose.yml && \
touch README.md && \
touch .gitignore
  • 上記コマンドを実行した後のディレクトリー構成は下記の状態と
.
├── backend/
│   ├── web1/
│   │   ├── settings/
│   │   │   ├── env
│   │   │   └── entrypoint.sh
│   │   ├── Dockerfile.dev
│   │   ├── Gemfile
│   │   └── Gemfile.lock
│   └── nginx/
│       ├── conf/
│       │   └── web1.conf
│       ├── error_page/
│       │   ├── 404.html
│       │   ├── 422.html
│       │   └── 500.html
│       └── Dockerfile
├── db/
│   └── conf/
│       └── .env
├── phpmyadmin/
│   └── conf/
│       └── .env
├── compose.yml
├── .gitignore
└── README.md

.gitignore

  • .gitignoreに、gitへ含めたくないファイルの情報を記載する
.gitignore
# db関連
mysql_volume

# .env関連
db/conf/.env
phpmyadmin/conf/.env
backend/web1/settings/.env

# その他
.DS_Store

Gem関連の設定

  • Gemfileに下記を記載
source "https://rubygems.org"
# 新規で立ち上げる際は、最新バージョンにする
gem "rails", "~> 8.0.1"
  • Gemfile.lockは、何も記載しない
# 何も記載しない
  • entrypoint.shに下記を記載
settings/entrypoint.sh
#!/bin/bash
set -e

# 古いPIDファイルやソケットファイルを削除
rm -rf /web1/tmp/{pids,sockets}

# 必要なディレクトリを作成
mkdir -p /web1/tmp/{pids,sockets}

# マイグレーションを実行
bundle exec rails db:migrate

exec "$@"
  • .env下記を記載
  • 下記の設定は、DBを接続する際に必要
settings/.env
HOST=db
DATABASE=web1_development
USER_NAME=root
PASSWORD=password

Docker関連の構築

  • Railsを起動するDockerfileに、下記を記載
# ベースイメージを指定
ARG RUBY_VERSION=3.4.1
FROM ruby:${RUBY_VERSION}-slim-bookworm

# 環境変数の設定
ENV LANG=C.UTF-8 \
    TZ=Asia/Tokyo \
    RUBY_YJIT_ENABLE=1 \
    RAILS_ENV=development \
    APP_ROOT_PATH=/web1

# 必要なパッケージをインストール
RUN apt-get update && apt-get install -y \
    build-essential \
    libgmp-dev \
    libssl-dev \
    libmariadb-dev-compat \
    curl \
    git \
    && rm -rf /var/lib/apt/lists/*

# 作業ディレクトリを設定
WORKDIR ${APP_ROOT_PATH}

# Gemfile と Gemfile.lock をコピーし、依存関係をインストール
COPY Gemfile Gemfile.lock ${APP_ROOT_PATH}/
RUN bundle install

# プロジェクトのソースコードをコピー
COPY . ${APP_ROOT_PATH}/

# エントリポイントスクリプトをコピーし、実行可能にする
COPY settings/entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]

Docker composeの構築

  • Railsの環境は、デバッグが出来る様に構築
  • phpmyadminは、DBの中身を可視化する為に入れています
compose.yml
services:
  nginx:
    container_name: nginx
    image: nginx:latest
    build: ./backend/nginx
    ports:
      - "80:80"
    volumes:
      - tmp_volume:/web1/tmp
    depends_on:
      - web1
  web1:
    container_name: web1
    image: web1:latest
    build:
      context: ./backend/web1
      dockerfile: Dockerfile.dev
    env_file:
      - ./backend/web1/settings/.env
    volumes:
      - ./backend/web1:/web1
      - tmp_volume:/web1/tmp
    ports:
      - 1111:1111  
    depends_on:
      - db
    tty: true
    stdin_open: true
    command: >
      bash -c "
        bundle install &&
        rails db:migrate &&
        rdbg -n --open --host 0.0.0.0 --port 1111 -c -- bundle exec pumactl start
      "
  db:
    container_name: mysql
    image: mysql:latest
    env_file: ./db/conf/.env
    volumes:
      - ./db/mysql_volume:/var/lib/mysql
    ports:
      - "3306:3306"
    restart: always
  phpmyadmin:
    container_name: phpmyadmin
    image: phpmyadmin:latest
    restart: always
    depends_on:
      - db
    env_file:
      - ./phpmyadmin/conf/.env  
    ports:
      - "8080:80"

volumes:
  tmp_volume:
    driver: local
    driver_opts:
      type: tmpfs
      device: tmpfs

volumes内で使用しているメソッドと説明

メソッド 説明
volumes Dockerコンテナで使用するボリュームを定義する。ボリュームを指定する事で、コンテナが停止・削除されてもデータを保持できる。ただし、一時的なボリューム(今回の場合は、tmpfs)を指定すると、コンテナ停止時にデータが消える。
tmp_volume volumesで定義されたボリュームの名前。コンテナ内で、この名前のボリュームをマウント(接続)できる。この設定では、一時的なメモリ上のストレージとして機能する。
driver ボリュームの管理方法(ドライバ)を指定する。localを指定すると、ホストマシンのストレージ(ディスクやメモリ)を利用する。特に、tmpfsと組み合わせるとメモリ上にデータを保持し、高速アクセスが可能になる。
driver_opts ボリュームの詳細な設定を行う為のオプション。ここで指定した内容により、ボリュームの動作が変わる。例えば、type=tmpfsを指定すると、ボリュームがメモリ上に作成され、ディスクを使用しない一時的なストレージとして機能する。
type ボリュームの種類を指定する。tmpfsを指定すると、ボリュームがRAM(メモリ)上に作成され、データの読み書き速度が高速になる。ただし、コンテナが停止するとデータは消える。
device 使用するストレージのデバイスを指定する。ここでは、tmpfsを指定している為、ディスクではなくメモリをストレージとして扱う事になる。これにより、ディスクI/Oを減らし、高速なデータ処理が可能になる。

Nginxの構築

  • Nginx用のDockerfileに、下記の内容を記載
FROM nginx:latest

RUN apt-get update && apt-get install -y curl
RUN rm /etc/nginx/conf.d/default.conf

COPY conf/web1.conf /etc/nginx/conf.d/web1.conf
COPY error_page /usr/share/nginx/html

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

  • conf/web1.confに、下記の内容を記載
# unixドメインソケット通信でpumaとnginxを接続する
upstream web1 {
	server unix:///web1/tmp/sockets/puma.sock;
}

server {
	listen 80;
	server_name localhost;

  location / {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $host;
    proxy_redirect off;
    proxy_pass http://web1;
    proxy_intercept_errors on;
  }

  # エラーページの設定
  error_page 404 /404.html;
  error_page 422 /422.html;
  error_page 500 502 503 504 /500.html;

  # 静的エラーページの内部設定
  location ~ ^/(404|422|500)\.html$ {
    root /usr/share/nginx/html;
    internal;
  }
}
  • 404.html422.html500.htmlを作成する
404.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>404 - ページが見つかりません</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            text-align: center;
            padding: 50px;
        }
        h1 {
            font-size: 50px;
        }
        p {
            font-size: 20px;
        }
        a {
            color: #007BFF;
            text-decoration: none;
        }
        a:hover {
            text-decoration: underline;
        }
    </style>
</head>
<body>
    <h1>404</h1>
    <p>お探しのページは見つかりませんでした。</p>
</body>
</html>
422.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>422 - 処理できない要求</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            text-align: center;
            padding: 50px;
        }
        h1 {
            font-size: 50px;
        }
        p {
            font-size: 20px;
        }
        a {
            color: #007BFF;
            text-decoration: none;
        }
        a:hover {
            text-decoration: underline;
        }
    </style>
</head>
<body>
    <h1>422</h1>
    <p>リクエストを処理できませんでした。</p>
</body>
</html>
500.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>500 - サーバーエラー</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            text-align: center;
            padding: 50px;
        }
        h1 {
            font-size: 50px;
        }
        p {
            font-size: 20px;
        }
        a {
            color: #007BFF;
            text-decoration: none;
        }
        a:hover {
            text-decoration: underline;
        }
    </style>
</head>
<body>
    <h1>500</h1>
    <p>内部サーバーエラーが発生しました。</p>
</body>
</html>

db(MySQL)の設定

  • dbディレクトリー内にある.envファイルへ、下記を記載する
conf/.env
MYSQL_DATABASE=web1_development
MYSQL_ROOT_PASSWORD=password
TZ=Asia/Tokyo

phpMyAdmin

  • phpmyAdminディレクトリー内にある.envファイルへ、下記を記載する
conf/.env
PMA_ARBITRARY=1
PMA_HOSTS=db
PMA_USER=root
PMA_PASSWORD=password

起動手順

新しいRailsアプリケーションを作成する

  • 下記のコマンドをcompose.ymlファイルの階層にてコマンドを実行する
  • web1のディレクトリー内に、Railsを立ち上げる為に必要なファイルとディレクトリーが生成される
docker compose run web1 rails new . \
  --force \
  --no-deps \
  --database=mysql \
  --css=tailwind \
  --skip-action-mailbox \
  --skip-action-mailer \
  --skip-test

Railsを立ち上げる為のコマンドと説明

コマンド 説明
docker compose run docker-compose.ymlの定義に基づいて、コンテナを作成・実行する
web1 docker-compose.ymlで定義された web1サービスのコンテナ内でコマンドを実行する
rails new . カレントディレクトリに新しいRailsアプリを作成する
--force 既存のファイルがあっても上書きする
--no-deps 依存する他のコンテナ(例: db 等)を起動せずに web1のみを実行する
--database=mysql データベースとしてMySQLを使用するようにRailsアプリを設定する
--css=tailwind CSSフレームワークとしてTailwind CSSを使用する
--skip-action-mailbox Action Mailbox(メール受信機能)のファイルを生成しない
--skip-action-mailer Action Mailer(メール送信機能)のファイルを生成しない
--skip-test デフォルトのテストファイルを生成しない
gem 'dotenv-rails'
  • 下記のファイルに、以下の修正を行う
  • backend/web1/settings/.envにある.envを読み込ませる設定を加える
backend/web1/config/application.rb

# 省略

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

if %w[development test].include?(Rails.env)
  Dotenv.load(File.expand_path('../settings/.env', __dir__))
end

module Web1

# 省略

  • database.ymlを下記の様に修正

database.ymlの注意点

  • developmentには、直接データーベースの設定を書いても良い
  • credentials.yml.encをデーターベースの設定を書いて運用しても良い
backend/web1/config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

development:
  <<: *default
  host:     <%= ENV["HOST"] %>
  database: <%= ENV["DATABASE"] %>
  username: <%= ENV["USER_NAME"] %>
  password: <%= ENV["PASSWORD"] %>

test:
  <<: *default
  database: web1_test

production:
  primary: &primary_production
    <<: *default
    database: web1_production
    username: web1
    password: <%= ENV["WEB1_DATABASE_PASSWORD"] %>
  cache:
    <<: *primary_production
    database: web1_production_cache
    migrations_paths: db/cache_migrate
  queue:
    <<: *primary_production
    database: web1_production_queue
    migrations_paths: db/queue_migrate
  cable:
    <<: *primary_production
    database: web1_production_cable
    migrations_paths: db/cable_migrate
  • pumaの設定を修正
backend/web1/config/puma.rb
threads_count = ENV.fetch("RAILS_MAX_THREADS", 3)
threads threads_count, threads_count
port ENV.fetch("PORT", 3000)
plugin :tmp_restart
plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"]
pidfile ENV["PIDFILE"] if ENV["PIDFILE"]

# 下記を追加する
app_root = File.expand_path("..", __dir__)
bind "unix://#{app_root}/tmp/sockets/puma.sock"
  • 下記の順番でコンテナーを立ち上げる
    1. 起動しているコンテナーを全て立ち下げる
    2. 再度、ビルドを行い、イメージを更新する
    3. コンテナを起動する
# コンテナーの立ち下げ
docker rm $(docker ps -aq)

# イメージをビルド
docker compose build --no-cache

# 不要なイメージを消去
docker images -f "dangling=true" -q | xargs docker rmi

# コンテナーの起動
docker compose up -d

スクリーンショット 2025-02-15 0.57.20.png

GitHub

おまけ

.vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "rdbg",
      "name": "Debug Ruby",
      "request": "attach",
      "debugPort": "localhost:1111",
      "localfsMap": "/web1:${workspaceFolder}",
    },
  ]
}

参考記事

感想

今回、以前から挑戦したかったRailsNginxソケット通信をローカル環境で構築しました。特に重要だったのは、ボリュームを利用して2つのコンテナ(RailsNginx)を共有させるという構成です。当初はこの仕組みが理解できず苦戦しましたが、試行錯誤を重ねる中で徐々に構造を把握し、最終的に念願だった環境を構築する事ができました。

今回の構成の様に、アプリケーションサーバーの前段で処理を行う手法には、様々な選択肢があります。引き続き理解を深め、より最適な環境構築に取り組んでいきたいと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?