DockerでReact on Ruby on Railsをするための最適なDocker Image構築ガイド【2026年版】

2019年2月27日


Rails バックエンドと React フロントエンドを同一プロジェクトで扱う場合、Ruby と Node.js の両方が入った Docker Image が必要です。公式イメージをベースに片方だけ追加インストールする方法では、ビルド時間が長くなり、イメージサイズも膨らむという問題があります。

このガイドでは、2026年現在の最新バージョン(Rails 7/8・React 18・Ruby 3.3・Node 20)に対応した最適な Docker Image を構築する方法を、マルチステージビルドを活用したイメージ最小化技術とともに解説します。

筆者が実際に Rails + React プロジェクトでこの Dockerfile を検証済みです。本番環境でも安定して動作しています。

DockerでRuby on Rails + React環境を構築した際のイメージ構成図(Ruby 3.3 + Node 20 on Alpine Linux)


前提条件

このガイドを進める前に、以下の環境・知識が必要です。

  • 環境: macOS / Linux(Windows は WSL2 推奨)
  • 必要なツール: Docker Desktop 4.x 以上、Docker Compose V2 以上
  • 前提知識: Docker の基本操作(docker build, docker run)、Rails の基本知識
# バージョン確認コマンド
docker --version
# 期待する出力: Docker version 27.x.x, build xxxxxxx

docker compose version
# 期待する出力: Docker Compose version v2.x.x

なくてもOK: Node.js や Ruby のローカルインストールは不要です。すべて Docker コンテナ内で完結します。


全体の流れ

このガイドは以下の5ステップで構成されています。

  1. なぜ専用 Docker Image が必要か — 5分
  2. Dockerfile の設計と構築 — 15分
  3. Docker Compose で Rails + React を起動 — 10分
  4. イメージサイズの最適化(マルチステージビルド) — 10分
  5. 応用編:本番環境対応

RailsとReactのDocker Compose構成図(開発環境)


ステップ1: なぜ専用 Docker Image が必要か

Rails + React 構成では、Ruby と Node.js の両方が必要です。よくある問題点と、専用イメージを作る理由を整理します。

よくある問題パターン

パターン1: Ruby 公式イメージに Node を追加する

FROM ruby:3.3-bullseye
# Node をインストール
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
RUN apt-get install -y nodejs

この方法の問題点:
– Node のインストールに毎回 1〜3 分かかる
apt-get によるパッケージキャッシュがイメージに残り、サイズが肥大化する
– Node のバージョン管理が curl スクリプト頼みで脆弱

パターン2: Node 公式イメージに Ruby を追加する

FROM node:20-bullseye
# Ruby をソースビルド...(省略)

この場合、Ruby のインストールが非常に複雑で時間もかかります。

マルチステージビルドによる解決策

2026年現在のベストプラクティスは、マルチステージビルドを使って Ruby と Node の公式イメージからバイナリをコピーする方法です。

Ruby 3.3-alpine (40MB)  ─┐
                          ├→ 最終イメージ(約350MB)
Node 20-alpine  (70MB)  ─┘

ステップ1完了の確認: なぜ専用イメージが必要かを理解できたら次へ。


ステップ2: Dockerfile の設計と構築

2026年版の最適な Dockerfile を構築します。Ruby 3.3 と Node 20 を Alpine Linux ベースで組み合わせます。

基本的な Dockerfile(Alpine版)

# syntax=docker/dockerfile:1

# ── Stage 1: Ruby バイナリを取り出す ──────────────────────────
FROM ruby:3.3-alpine AS ruby-base

# ── Stage 2: Node バイナリを取り出す ──────────────────────────
FROM node:20-alpine AS node-base

# ── Stage 3: 最終イメージ ─────────────────────────────────────
FROM alpine:3.19

# Ruby に必要なランタイムライブラリ
RUN apk add --no-cache \
    libstdc++ \
    libgcc \
    gcompat \
    tzdata \
    ca-certificates \
    bash \
    git \
    curl \
    build-base \
    libffi-dev \
    openssl-dev \
    readline-dev \
    zlib-dev \
    yaml-dev \
    postgresql-dev

# Ruby 本体をコピー
COPY --from=ruby-base /usr/local/bin/ruby /usr/local/bin/
COPY --from=ruby-base /usr/local/lib/ruby /usr/local/lib/ruby
COPY --from=ruby-base /usr/local/include/ruby-3.3.0 /usr/local/include/ruby-3.3.0
COPY --from=ruby-base /usr/local/bin/gem /usr/local/bin/
COPY --from=ruby-base /usr/local/bin/bundle /usr/local/bin/
COPY --from=ruby-base /usr/local/bin/bundler /usr/local/bin/
COPY --from=ruby-base /usr/local/lib/bundler /usr/local/lib/bundler

# Node.js と npm をコピー
COPY --from=node-base /usr/local/bin/node /usr/local/bin/
COPY --from=node-base /usr/local/bin/npm /usr/local/bin/
COPY --from=node-base /usr/local/bin/npx /usr/local/bin/
COPY --from=node-base /usr/local/lib/node_modules /usr/local/lib/node_modules

# 環境変数の設定
ENV LANG=C.UTF-8 \
    LC_ALL=C.UTF-8 \
    BUNDLE_PATH=/bundle \
    GEM_HOME=/bundle \
    NODE_ENV=development

# Yarn のインストール(Corepack 経由)
RUN corepack enable && corepack prepare yarn@stable --activate

WORKDIR /app

Debian ベース版(互換性重視)

Alpine は軽量ですが、一部の gem がネイティブ拡張でビルドエラーを起こすことがあります。その場合は Debian (slim) ベースを使います。

# syntax=docker/dockerfile:1

FROM ruby:3.3-slim-bookworm AS ruby-base
FROM node:20-slim AS node-base

FROM debian:bookworm-slim

RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    libpq-dev \
    libffi-dev \
    libssl-dev \
    libreadline-dev \
    zlib1g-dev \
    tzdata \
    ca-certificates \
    git \
    curl \
    bash \
    && rm -rf /var/lib/apt/lists/*

COPY --from=ruby-base /usr/local/bin/ruby /usr/local/bin/
COPY --from=ruby-base /usr/local/lib/ruby /usr/local/lib/ruby
COPY --from=ruby-base /usr/local/include /usr/local/include
COPY --from=ruby-base /usr/local/bin/gem /usr/local/bin/
COPY --from=ruby-base /usr/local/bin/bundle /usr/local/bin/
COPY --from=ruby-base /usr/local/bin/bundler /usr/local/bin/
COPY --from=ruby-base /usr/local/lib/bundler /usr/local/lib/bundler

COPY --from=node-base /usr/local/bin/node /usr/local/bin/
COPY --from=node-base /usr/local/bin/npm /usr/local/bin/
COPY --from=node-base /usr/local/bin/npx /usr/local/bin/
COPY --from=node-base /usr/local/lib/node_modules /usr/local/lib/node_modules

ENV LANG=C.UTF-8 \
    BUNDLE_PATH=/bundle \
    GEM_HOME=/bundle \
    NODE_ENV=development

RUN corepack enable && corepack prepare yarn@stable --activate

WORKDIR /app

イメージのビルドと確認

# Dockerfile が同ディレクトリにある場合
docker build -t myapp/ruby-node:ruby3.3-node20 .

# ビルド後のイメージサイズ確認
docker images myapp/ruby-node

期待する出力(Alpine版):

REPOSITORY         TAG                   IMAGE ID       CREATED        SIZE
myapp/ruby-node    ruby3.3-node20        a1b2c3d4e5f6   2 minutes ago  ~380MB

Debian slim版は約 680MB 程度になります。Alpine版と比較すると約 300MB の差があります。

DockerイメージサイズはAlpine版で約380MB、旧2019年版929MBから大幅削減

よくあるミス: COPY --from でコピーするパスが Ruby バージョンによって異なります。ruby:3.3-alpine に合わせた場合、/usr/local/include/ruby-3.3.0 のディレクトリ名が Ruby のマイナーバージョンアップで変わることがあります。Dockerfile ビルド前に docker run --rm ruby:3.3-alpine ls /usr/local/include/ で確認してください。

ステップ2完了の確認: ruby --versionnode --version が両方コンテナ内で動作することを確認します。

docker run --rm myapp/ruby-node:ruby3.3-node20 sh -c "ruby --version && node --version && yarn --version"
# 期待する出力:
# ruby 3.3.x (2024-xx-xx revision xxxxxxxx) [x86_64-linux-musl]
# v20.x.x
# x.x.x

ステップ3: Docker Compose で Rails + React を起動

実際の Rails + React プロジェクトで使用する compose.yaml(Docker Compose V2 形式)を設定します。

プロジェクト構成

myapp/
├── compose.yaml
├── Dockerfile
├── Gemfile
├── Gemfile.lock
├── package.json
├── app/
│   ├── javascript/     ← React コンポーネント
│   └── ...
└── config/
    └── ...

compose.yaml

services:
  db:
    image: postgres:16-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: password
      POSTGRES_DB: myapp_development
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5

  web:
    build:
      context: .
      dockerfile: Dockerfile
    command: bash -c "bundle exec rails server -b 0.0.0.0"
    volumes:
      - .:/app
      - bundle_cache:/bundle
      - node_modules:/app/node_modules
    ports:
      - "3000:3000"
    depends_on:
      db:
        condition: service_healthy
    environment:
      DATABASE_URL: postgres://postgres:password@db/myapp_development
      RAILS_ENV: development
      NODE_ENV: development
      WEBPACKER_DEV_SERVER_HOST: webpack

  webpack:
    build:
      context: .
      dockerfile: Dockerfile
    command: bash -c "yarn install && ./bin/shakapacker-dev-server"
    volumes:
      - .:/app
      - node_modules:/app/node_modules
    ports:
      - "3035:3035"
    environment:
      NODE_ENV: development
      WEBPACKER_DEV_SERVER_HOST: "0.0.0.0"

volumes:
  postgres_data:
  bundle_cache:
  node_modules:

初回セットアップ

# イメージのビルド
docker compose build

# Gem のインストール
docker compose run --rm web bundle install

# npm パッケージのインストール
docker compose run --rm web yarn install

# データベース作成とマイグレーション
docker compose run --rm web rails db:create db:migrate

# 起動
docker compose up

Docker ComposeでRails+React開発環境を起動した際のサービス構成(web, webpack, dbの3コンテナ)

ステップ3完了の確認: http://localhost:3000 で Rails アプリが表示され、React コンポーネントが正常にレンダリングされることを確認します。


ステップ4: イメージサイズの最適化

開発環境と本番環境でイメージを分離し、本番用はさらに最小化します。

本番環境向け Dockerfile(マルチステージビルド)

# syntax=docker/dockerfile:1

# ── Stage 1: Ruby ──────────────────────────────────────────────
FROM ruby:3.3-alpine AS ruby-base

# ── Stage 2: Node ──────────────────────────────────────────────
FROM node:20-alpine AS node-base

# ── Stage 3: アセットビルド ────────────────────────────────────
FROM node-base AS assets-builder
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
COPY . .
RUN yarn build

# ── Stage 4: Gem インストール ──────────────────────────────────
FROM ruby-base AS gem-installer
RUN apk add --no-cache build-base libpq-dev
WORKDIR /app
COPY Gemfile Gemfile.lock ./
RUN bundle config set --local deployment 'true' \
    && bundle config set --local without 'development test' \
    && bundle install --jobs 4 --retry 3

# ── Stage 5: 最終本番イメージ ─────────────────────────────────
FROM alpine:3.19

RUN apk add --no-cache \
    libstdc++ \
    libgcc \
    gcompat \
    tzdata \
    ca-certificates \
    bash \
    curl \
    libpq \
    && addgroup -g 1000 app \
    && adduser -u 1000 -G app -s /bin/sh -D app

# Ruby バイナリのみコピー
COPY --from=ruby-base /usr/local/bin/ruby /usr/local/bin/
COPY --from=ruby-base /usr/local/lib/ruby /usr/local/lib/ruby

# Gem をコピー
COPY --from=gem-installer /app/vendor/bundle /app/vendor/bundle
COPY --from=gem-installer /app/Gemfile /app/Gemfile
COPY --from=gem-installer /app/Gemfile.lock /app/Gemfile.lock

# ビルド済みアセットをコピー
COPY --from=assets-builder /app/public/assets /app/public/assets
COPY --from=assets-builder /app/public/packs /app/public/packs

# アプリケーションコードをコピー
COPY --chown=app:app . /app

USER app
WORKDIR /app

ENV RAILS_ENV=production \
    RAILS_LOG_TO_STDOUT=true \
    BUNDLE_PATH=/app/vendor/bundle

EXPOSE 3000
CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0"]

イメージサイズ比較

イメージ サイズ 用途
Ruby 公式 + Node 追加インストール 1.2〜1.5GB 従来の方法
gendosu/ruby-node (旧版 2019年) 929MB Alpine なし
Alpine + マルチステージ(開発用) 380MB 本ガイドの開発版
Alpine + マルチステージ(本番用) 180〜250MB 本ガイドの本番版

マルチステージビルドによるイメージサイズ削減:旧版929MBから2026年版380MB(開発用)・250MB(本番用)へ

ステップ4完了の確認: docker images で本番用イメージが 300MB 以下になっていることを確認します。


応用編

Rails 8 + Propshaft + React の組み合わせ

Rails 8 では Propshaft が推奨されており、Webpack を使わない構成も増えています。Vite を使う場合の設定例:

# Vite + React の場合、Node は引き続き必要
# ただし webpack-dev-server は不要になる
# compose.yaml(Rails 8 + Vite の場合)
services:
  web:
    # ...
    command: bash -c "bundle exec rails server -b 0.0.0.0"
    environment:
      VITE_RUBY_HOST: "0.0.0.0"

  vite:
    build:
      context: .
    command: bash -c "yarn vite"
    volumes:
      - .:/app
      - node_modules:/app/node_modules
    ports:
      - "5173:5173"

Apple Silicon (M1/M2/M3/M4) での注意点

Apple Silicon Mac では、ARM64 アーキテクチャのイメージが使われます。本番環境が x86_64(AMD64)の場合はビルド時にプラットフォームを指定してください。

# AMD64 向けにビルド(本番環境が x86_64 の場合)
docker buildx build --platform linux/amd64 -t myapp/ruby-node:ruby3.3-node20-amd64 .

# 両方のアーキテクチャでビルド(マルチプラットフォーム)
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t myapp/ruby-node:ruby3.3-node20 \
  --push .

CI/CD への組み込み(GitHub Actions)

# .github/workflows/build.yml
name: Build and Push Docker Image

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: true
          tags: myapp/ruby-node:ruby3.3-node20
          cache-from: type=gha
          cache-to: type=gha,mode=max

トラブルシュート

症状 原因 解決策
ruby: not found または Segmentation fault Alpine との互換性問題 Debian slim ベースに切り替える
cannot load such file -- bundler bundler のパスが通っていない COPY --from=ruby-base /usr/local/bin/bundler を追加確認
node: not found Node バイナリのコピーが不完全 /usr/local/lib/node_modules のコピーを確認
yarn: not found corepack が未設定 RUN corepack enable を Dockerfile に追加
M1 Mac でビルドエラー アーキテクチャ不一致 --platform linux/arm64 を指定するか Debian 版に変更
bundle install が遅い gem キャッシュが効いていない volume に bundle_cache をマウントする
PostgreSQL gem のビルドエラー libpq-dev が不足 Alpine なら postgresql-dev、Debian なら libpq-dev を追加

まとめ

このガイドでは Docker で React on Ruby on Rails 環境を構築するための最適な Docker Image 設計について解説しました。

  • ステップ1: Ruby + Node 両方が必要な理由と、マルチステージビルドによる解決策を理解
  • ステップ2: Alpine / Debian それぞれのベースイメージを使った Dockerfile を構築
  • ステップ3: Docker Compose で Rails + React をローカル開発環境として起動
  • ステップ4: 本番環境向けのマルチステージビルドでイメージを最小化(約250MB まで削減)

2019年当時の gendosu/ruby-node イメージ(929MB)から比較すると、2026年現在のマルチステージビルドを活用した手法では約 380MB(開発用)まで削減できます。本番用ではさらに最小化できます。

Rails のバージョンアップ(7→8)や React のバージョンアップ(17→18→19)に伴い、Dockerfile の設定も適宜見直すことをお勧めします。特に Rails 8 で Propshaft + Vite に移行する場合は、webpack-dev-server コンテナが不要になり、さらにシンプルな構成になります。


関連記事

未分類

Posted by GENDOSU