1
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 Compose で単一のNginx から複数の Ruby on Rails を公開する環境を構築

1
Last updated at Posted at 2026-01-30

記事概要

本記事では、Docker および Docker Compose を用いて、単一サーバー上において Nginx をリバースプロキシとして利用し、複数の Ruby on Rails アプリケーションを同時に動作させる開発環境の構築手法を示す。
本構成により、以下の事項について実践的な理解を深めることを目的とする。

  • 単一マシン上で複数の Rails アプリケーションを安全に公開する構成手法
  • リバースプロキシの動作原理およびリクエストルーティングの仕組み
  • Docker 環境におけるポートフォワーディングおよび Host ヘッダーの挙動

リバースプロシキの概要

利用者(ブラウザ)と Web アプリケーションの間に入り、通信を中継するサーバーの仕組み。
通常は、ブラウザから直接 Ruby on Rails などのアプリケーションサーバーにアクセスする。
しかし、リバースプロキシを利用することで、ブラウザは Nginx にアクセスし、Nginx が裏側の Rails サーバーへ リクエストを転送する構成となる。
この仕組みにより、Nginx が通信の窓口となり、適切な Rails アプリケーションへリクエストを振り分ける役割を担う。

リバースプロキシのメリット

  • 複数の Web アプリを 1 つのサーバーで公開できる
  • SSL(HTTPS)設定を Nginx に集約できる
  • 負荷分散やキャッシュなどの高度な機能を利用できる
  • アプリケーションサーバーを直接インターネットに公開しないで使用できる

プロキシとの違い

リバースプロキシは、プロキシとは通信方向が逆であるため「リバース」と呼ばれる。

  • プロキシはクライアント側の代理サーバー
  • リバースプロキシはサーバー側の代理サーバー

構築時の前提条件

  • Dockerが正常に動作する
  • 2台以上のRailsサーバーを用意する

構築手順

構築完了後のディレクトリー構成は以下の構成とする。

ディレクトリー構成

.
├── backend/
│   ├── nginx/              # リバースプロキシ
│   │   ├── conf/web.conf   # Nginx 設定
│   │   ├── error_page/     # カスタムエラーページ
│   │   └── Dockerfile
│   │
│   ├── web1/               # Rails アプリ1
│   ├── web2/               # Rails アプリ2
│   └── web3/               # Rails アプリ3
│
├── db/                     # PostgreSQL 永続化領域
│   ├── conf/.env           # DB 環境変数
│   └── postgresql_volume/  # コンテナー立ち上げ時に自動生成
│       └── 18/docker/
│
├── pgadmin/                # 任意で導入
│   ├── conf/.env
│   └── pgadmin_volume/     # コンテナー立ち上げ時に自動生成
│
├── compose.yml
├── .gitignore
└── README.md

Dockerfileの設定

以下のディレクトリに、それぞれ同一内容の Dockerfile を作成する。ただし、APP_ROOT の値のみ各ディレクトリ名に対応した値へ変更する。それ以外の設定はすべて共通とする。

  • backend/web1/Dockerfile
  • backend/web2/Dockerfile
  • backend/web3/Dockerfile

注意
本記事では構成の簡略化のため同一内容の Dockerfile を使用している。
実運用や検証用途に応じて、各アプリケーションごとに Dockerfile の内容を変更しても問題ない。

ARG RUBY_VERSION=4.0.1
FROM ruby:${RUBY_VERSION}-slim-trixie

ENV LANG=C.UTF-8 \
    TZ=Asia/Tokyo \
    RUBY_YJIT_ENABLE=1 \
    RUBY_ENV=development \
    APP_ROOT=/web # ← 変更部分

RUN apt-get update -qq && apt-get install -y \
    build-essential \
    libpq-dev \
    libyaml-dev \
    pkg-config \
    git \
    nodejs \
    curl \
    npm \
    vim \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

WORKDIR ${APP_ROOT}
COPY Gemfile Gemfile.lock ./
RUN gem install bundler && \
    bundle install --jobs 4 --retry 3
COPY . .
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]

Docker-composeの設定

ルートディレクトリーに配置し、以下の設定を入れる。

注意
複数コンテナから同時にマイグレーションを実行すると、ActiveRecord の排他制御(同時実行を防ぐためのロック機構)により ConcurrentMigrationError が発生する。そのため、

services:
  db:
    container_name: postgresql
    image: postgres:latest
    env_file: ./db/conf/.env
    volumes:
      - ./db/postgresql_volume:/var/lib/postgresql
    ports:
      - "5432:5432"
    restart: always

  pgadmin:
    container_name: pgadmin
    image: dpage/pgadmin4
    restart: always
    volumes:
      - ./pgadmin/pgadmin_volume:/var/lib/pgadmin
    env_file:
      - ./pgadmin/conf/.env
    ports:
      - 8081:80
    depends_on:
      - db

  nginx:
    container_name: nginx
    build: ./backend/nginx
    image: nginx:latest
    pull_policy: never
    ports:
      - 81:81
      - 82:82
      - 83:83
    volumes:
      - web1_socket_volume:/web1/tmp
      - web2_socket_volume:/web2/tmp
      - web3_socket_volume:/web3/tmp
    depends_on:
      - web1
      - web2
      - web3

  web1:
    container_name: web1
    image: web1:latest
    pull_policy: never
    build:
      context: ./backend/web1
      dockerfile: Dockerfile
    ports:
      - 1111:1111
    volumes:
      - ./backend/web1:/web1
      - web1_socket_volume:/web1/tmp
    environment:
      APP_NAME: web1
    depends_on:
      - db
    tty: true
    stdin_open: true
    command:  >
      bash -c "
        rm -rf /web1/tmp/{pids,sockets} &&
        mkdir -p /web1/tmp/{pids,sockets} &&
        rails db:migrate &&
        bundle exec pumactl start
      "

  web2:
    container_name: web2
    image: web2:latest
    build:
      context: ./backend/web2
      dockerfile: Dockerfile
    pull_policy: never
    ports:
      - 2222:2222
    volumes:
      - ./backend/web2:/web2
      - web2_socket_volume:/web2/tmp
    environment:
      APP_NAME: web2
    depends_on:
      - db
    tty: true
    stdin_open: true
    command:  >
      bash -c "
        rm -rf /web2/tmp/{pids,sockets} &&
        mkdir -p /web2/tmp/{pids,sockets} &&
        bundle exec pumactl start
      "

  web3:
    container_name: web3
    image: web3:latest
    build:
      context: ./backend/web3
      dockerfile: Dockerfile
    pull_policy: never
    ports:
      - 3333:3333
    volumes:
      - ./backend/web3:/web3
      - web3_socket_volume:/web3/tmp
    environment:
      APP_NAME: web3
    depends_on:
      - db
    tty: true
    stdin_open: true
    command:  >
      bash -c "
        rm -rf /web3/tmp/{pids,sockets} &&
        mkdir -p /web3/tmp/{pids,sockets} &&
        bundle exec pumactl start
      "

x-tmpfs-volume: &tmpfs-volume
  driver: local
  driver_opts:
    type: tmpfs
    device: tmpfs

volumes:
  web1_socket_volume:
    name: web1_puma_socket
    <<: *tmpfs-volume

  web2_socket_volume:
    name: web2_puma_socket
    <<: *tmpfs-volume

  web3_socket_volume:
    name: web3_puma_socket
    <<: *tmpfs-volume

Top-level volumes sectionの説明

メソッド メソッドの説明
x-tmpfs-volume: Compose Extension Field(拡張フィールド)
x- で始まるキーは Docker Compose に無視される特別な設定。実際の設定をまとめておく「テンプレートの定義(共通設定のひな形)」として利用できる
&tmpfs-volume YAML Anchor
設定ブロックに名前を付ける仕組みで、この設定セットに tmpfs-volume という名前を付ける
*tmpfs-volume YAML Alias
Anchor で定義した設定を参照する仕組み。tmpfs-volume で設定した値をここで使用する
<<: *tmpfs-volume YAML Merge Key
Alias で参照した設定内容を現在の設定に展開し、設定を合成する機能。
volumes: Top-level Volumes
Docker Compose で使用する Named Volume(名前付きボリューム)を定義するセクション。複数コンテナ間で共有できるストレージを作成する。
name: Volume Name
Docker 上に作成される実際のボリューム名を固定し、Compose による自動命名(プロジェクト名付き)を防ぐための設定。
driver: local Volume Driver
Docker 標準の local ドライバ(ホスト上のストレージを利用するデフォルト実装)を使用する
driver_opts: Driver Options
ボリュームドライバに追加パラメータを渡すための設定項目。
driver_opts.type=tmpfs
device=tmpfs
tmpfs ボリューム
ディスクではなく RAM(メモリ)上に作成される一時ストレージであり、コンテナ停止やボリューム削除時にデータが消失する。

Nginxの設定

本設定により、Nginx と複数の Rails アプリケーション間の通信を構成する。

  • Nginx から web1・web2・web3 の Puma に Unix ソケット経由で接続する
  • アクセスされたポート番号(81, 82, 83)に応じて転送先 Rails アプリを切り替える
  • ブラウザからの HTTP リクエストを Nginx が受け取り、Rails に中継するリバースプロキシ構成を実現する
Browser
   ↓ :81 / :82 / :83
Nginx (map で振り分け)
   ↓ Unix Socket
Puma (web1 / web2 / web3)

backend/nginx/confの配下に、web.confを作成し、以下の内容を追加する。

# web1のコンテナを Puma と Unix ソケットで接続する設定
upstream web1 {
	server unix:///web1/tmp/sockets/puma.sock;
}

# web2のコンテナを Puma と Unix ソケットで接続する設定
upstream web2 {
  server unix:///web2/tmp/sockets/puma.sock;
}

# web3のコンテナを Puma と Unix ソケットで接続する設定
upstream web3 {
  server unix:///web3/tmp/sockets/puma.sock;
}

# ポート番号ごとに転送先の Rails アプリを切り替える設定
map $server_port $upstream {
  81 web1;
  82 web2;
  83 web3;
  default web1; # 想定外のポートは web1 にフォールバック
}

server {
  # Nginx が受信する TCPポートを定義し、ポート番号に基づくリクエストの分岐を可能にする
  listen 81;
  listen 82;
  listen 83;
  server_name localhost; # サーバー名(ローカル開発用)

  location / {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # クライアント IP を Rails 側に渡す
    proxy_set_header Host $http_host;                            # Host ヘッダー(ホスト名とポート)を Rails に渡す
    proxy_redirect off;                                          # リダイレクトヘッダーを書き換えない
    proxy_pass http://$upstream;                                 # ポート番号に応じた upstream(Rails)へ転送
    proxy_intercept_errors on;                                   # Rails 側のエラーを Nginx で処理できるようにする
  }

  # エラーページの設定
  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; # HTML ファイルの配置場所
    internal;                   # 外部から直接アクセス不可(内部専用)
  }
}

Railsの設定

pumaの設定

以下に設置されているpuma.rbに下記の設定を追加する。この設定により、Rails の **Web サーバー(Puma)**はポートではなく puma.sock というファイルを通じて Nginx からの通信を受け取る。

  • backend/web1/config/puma.rb
  • backend/web2/config/puma.rb
  • backend/web3/config/puma.rb
puma.rb
app_root = File.expand_path("..", __dir__)
bind "unix://#{app_root}/tmp/sockets/puma.sock"

追加した設定のコード説明

メソッド(コード) 説明
File.expand_path("..", __dir__) 現在のファイルが置かれているディレクトリの一つ上の階層を取得し、アプリケーションのルートディレクトリの絶対パスを求める。
__dir__ この Ruby ファイルが保存されているフォルダの場所(パス)を取得するための特別な変数。
bind "unix://#{app_root}/tmp/sockets/puma.sock" Puma が通信を待ち受ける場所を Unix ソケットファイルに設定する。ポート番号ではなくファイルで通信を受け付ける設定である。

Unix ソケットファイルの概要

  • 同じサーバー内で動作するプログラム同士が通信するために使用される ファイル形式の通信口
  • 通常の Web サーバーはポート番号(例: 3000)を用いて通信を行うが、Unix ソケットファイルはファイルパス(例: /tmp/puma.sock)を通信先として利用する点が特徴

この仕組みは プロセス間通信(IPC: Inter-Process Communication) の一種であり、同一ホスト内で高速かつ安全にデータをやり取りするために利用される。
特に、Nginx と Puma のような Web サーバー構成 では、TCP ポート通信の代わりに Unix ソケットファイルを用いることで、オーバーヘッド(通信のための余計な処理)を削減し、セキュリティを向上させることができる。

database.ymlの設定

以下に配置されている database.yml に、下記の設定を追加する。

  • backend/web1/config/database.yml
  • backend/web2/config/database.yml
  • backend/web3/config/database.yml

注意
本記事では構成の簡略化のため、すべての Rails アプリケーションで同一の database.yml を使用している。
実運用や検証用途に応じて、データベース名やユーザー、接続先ホストなどをアプリケーションごとに分離しても問題ない。

default: &default
  adapter: postgresql
  encoding: unicode
  max_connections: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  port: 5432

development:
  <<: *default
  host:     db
  database: web_development
  username: test_user1
  password: test1234

production:
  primary: &primary_production
    <<: *default
    database: web_production
    username: web
    password: <%= ENV["WEB1_DATABASE_PASSWORD"] %>
  cache:
    <<: *primary_production
    database: web_production_cache
    migrations_paths: db/cache_migrate
  queue:
    <<: *primary_production
    database: web_production_queue
    migrations_paths: db/queue_migrate
  cable:
    <<: *primary_production
    database: web_production_cable
    migrations_paths: db/cable_migrate

route.rbとviewファイルの設定

任意処理
本手順は、複数の Rails アプリが正しく振り分けられていることを確認するために「表示確認用ページ」として追加した。
そのため、デモや動作確認を目的とする場合にのみ使用する。
既にアプリ側のルーティングや画面が存在する場合、または実運用の構成に組み込む場合は、本手順は不要。

各 Rails アプリのコンテナ内で、以下のコマンドを実行する。

bin/rails g controller Home index

それぞれのコンテナーのconfig/routes.rbを編集する。

config/routes.rb
Rails.application.routes.draw do
  root "home#index"
end

以下に設置されているindex.html.erbに下記の設定を追加する。

  • backend/web1/app/views/home/index.html.erb
  • backend/web2/app/views/home/index.html.erb
  • backend/web3/app/views/home/index.html.erb
<style>
  .page-bg {
    position: fixed;
    inset: 0;
    z-index: 9998;
    background: radial-gradient(circle at top, #111827, #020617);
  }

  .card {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 9999;

    background: rgba(17, 24, 39, 0.88);
    backdrop-filter: blur(10px);

    border-radius: 16px;
    padding: 20px 24px;

    width: min(520px, calc(100vw - 32px));
    border: 1px solid rgba(255, 255, 255, 0.08);
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6);

    color: #e5e7eb;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  }

  h1 {
    font-size: 30px;
    font-weight: 700;
    margin: 0 0 34px 0;
    color: #facc15;
    letter-spacing: -0.02em;
    text-align: center;
  }

  .row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 40px;

    padding: 20px 0;
    border-top: 1px solid rgba(255, 255, 255, 0.06);
  }

  .row:first-of-type {
    border-top: none;
  }

  .label {
    font-size: 20px;
    font-weight: 650;
    color: rgba(229, 231, 235, 0.55);
    letter-spacing: 0.01em;
  }

  .value {
    font-size: 20px;
    font-weight: 650;
    color: #f9fafb;
    text-align: right;
  }

  .mono {
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
    letter-spacing: 0.01em;
  }

  .pill {
    display: inline-block;
    margin-left: 18px;
    padding: 10px 20px;
    border-radius: 999px;
    background: #dc2626;
    color: white;
    font-size: 18px;
    font-weight: 800;
    vertical-align: middle;
  }
</style>

<div class="page-bg"></div>

<div class="card">
  <h1>Rails Application Information</h1>

  <div class="row">
    <div class="label">App</div>
    <div class="value">
      <span class="pill">Rails</span>
    </div>
  </div>

  <div class="row">
    <div class="label">Container</div>
    <div class="value"><%= ENV.fetch("APP_NAME", "web1") %></div>
  </div>

  <div class="row">
    <div class="label">Host</div>
    <div class="value"><%= request.host_with_port %></div>
  </div>

  <div class="row">
    <div class="label">Path</div>
    <div class="value"><%= request.fullpath %></div>
  </div>
</div>

疎通確認

以下の URL にアクセスし、それぞれ対応する Rails アプリケーションの画面が表示されることを確認する。ポート番号ごとに異なる Rails アプリケーションが表示されれば、Nginx のリバースプロキシ設定およびポートベースのルーティングが正しく機能している。

http://localhost:81/

web1.png

http://localhost:82/

web2.png

http://localhost:83/

web3.png

GitHub

参考資料

公式

非公式

感想

今回、Nginx 1 台で複数の Rails サーバーにアクセスする構成を構築しました。以前から試してみたかった構成だったため、実際に動かせたことに加えて、記事としてまとめられた点も含めてとても満足しています。
今後は、より実運用に近い構成や、Host ヘッダーやパスベースルーティングなど別のルーティング方式にも挑戦してみたいと思います。

1
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
1
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?