🔖

Next.js + Rails API を Docker コンテナで構築する手順

に公開

はじめに

昨今、フロントエンドに React(Next.js)を、バックエンドに Rails API を組み合わせた構成を見かける様になりました。

本記事では、「ホストマシンに Ruby や Node.js を一切インストールせず、すべて Docker コンテナ上で構築・起動する環境」をゼロから用意する手順を解説します。

本記事の手順に沿って必要なファイルを揃えれば、最後に docker compose up --build だけで開発環境が立ち上がり、Rails API と Next.js の開発サーバーを同時に動かせます。

また、本記事はあくまで開発の環境構築であり、本番環境の構築については記載しておりませんのでご留意ください。

対象読者

  • Next.js の開発をこれから勉強する方
  • Rails を API モードで動かしつつ、フロントを Next.js で実装したい方

前提環境・バージョン

  • MacBook Pro(2019)
  • macOS:Sonoma 14.4
  • Docker … 20.10 以降
  • Docker Compose … 1.29 以降
  • Ruby … 3.3.6
  • Rails … 7.2.0(API モード)
  • PostgreSQL … 15
  • Node.js … 23
  • Next.js … 15.x
  • Tailwind CSS … 対応する Next.js 15 系向けバージョン

開発環境の全体像

  1. Docker コンテナで Rails 7.2(API モード)+PostgreSQL 15 を動かす
  2. Docker コンテナで Next.js(TypeScript+Tailwind+App Router)を動かす
  3. docker-compose で3つ(DB/Rails/Next.js)を一括管理し、一発で起動する
  4. Rails は database.yml を環境変数経由で切り替え、TCP 接続で DB にアクセス
  5. Next.js は環境変数 NEXT_PUBLIC_API_URL を介して、開発マシン(ブラウザ)側からは http://localhost:3001コンテナ間通信 では http://backend:3000にアクセス

以後、具体的な手順を順番にご紹介します。

1. プロジェクトルート(モノレポ用ディレクトリ)を作成する

まずは開発を進める場所として、任意のフォルダを用意します。
本記事では例として ~/projects/personal/dummy-app を使います。

mkdir -p ~/projects/personal/dummy-app
cd ~/projects/personal/dummy-app

この時点ではまだ何もファイルがない状態です。
リポジトリとして初期化していたり、クリーンなままでも構いません。

2. Rails 7.2(Ruby 3.3.6)+API モード プロジェクトを生成する

次に、Docker コンテナ上で Rails API プロジェクトを作成します。
以下のコマンドを実行する事で、コンテナ上に Ruby 3.3.6+Rails 7.2 を用意できます。
使用したいバージョンが決まっている場合は、下記の数値を任意のバージョンに変更して下さい。

docker run --rm \
  -v "$(pwd)":/app \
  -w /app \
  ruby:3.3.6 \
  bash -lc "gem install rails -v 7.2.0 && /usr/local/bundle/bin/rails _7.2.0_ new backend --api --database=postgresql"
  • docker run --rm:コマンド実行後、コンテナを自動で削除する
  • v "$(pwd)":/app:ホスト側のカレントディレクトリ(今回なら ~/projects/personal/dummy-app)をコンテナ内の /app にマウント
  • w /app:コンテナ内の作業ディレクトリを /app に設定
  • ruby:3.3.6:Ruby 3.3.6 の公式 Docker イメージを使用
  • bash -lc "gem install rails -v 7.2.0 && ...":Rails 7.2.0 をインストール後、API モード+PostgreSQL 設定で backend/ フォルダに新規プロジェクトを作成

実行が終わると、 backend/ というディレクトリが自動生成されます。念のため中身を確認しておきましょう。

ls
# backend と表示されていればOK

backend/ 以下には、app/config/db/Gemfile など、Rails プロジェクトのひな形が入っています。

3. Next.js(TypeScript+Tailwind)プロジェクトを生成する

続いて、フロントエンド用に Next.js プロジェクトを作成します。

下記のコマンドを用いて Docker コンテナ上にひな形を自動生成できます。

docker run --rm \
  -v "$(pwd)":/app \
  -w /app \
  node:23 \
  bash -lc "npx create-next-app@15 frontend --typescript --eslint --tailwind --src-dir --app-dir --yes"
  • node:23:Node.js 23 の公式 Docker イメージを使用
  • npx create-next-app@15 frontend ... --yes:Next.js 15 系のプロジェクトを frontend/ というフォルダに一括生成します。-typescript-eslint-tailwind-src-dir-app-dir を指定し、対話プロンプトをすべて yes でスキップしています。

実行後、ホスト側に frontend/ ディレクトリが作成されます。中を確認してみてください。

ls
# backend  frontend と表示されればOk

frontend/ には、package.jsontsconfig.jsonsrc/public/ など、Next.js のひな形が入っています。

4. backend/config/database.yml を TCP 接続+.env 前提に書き換える

既存の developmenttest を以下の通り置き換えてください。

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

development:
  <<: *default
  database: <%= ENV.fetch("DATABASE_NAME") { "backend_development" } %>
  host:     <%= ENV.fetch("DATABASE_HOST")     %>    # .env で指定
  username: <%= ENV.fetch("DATABASE_USER")     %>    # .env で指定
  password: <%= ENV.fetch("DATABASE_PASSWORD") %>    # .env で指定

test:
  <<: *default
  database: <%= ENV.fetch("DATABASE_NAME") { "backend_test" } %>
  host:     <%= ENV.fetch("DATABASE_HOST")     %>
  username: <%= ENV.fetch("DATABASE_USER")     %>
  password: <%= ENV.fetch("DATABASE_PASSWORD") %>

production:
  <<: *default
  database: backend_production
  username: backend
  password: <%= ENV["BACKEND_DATABASE_PASSWORD"] %>
  • 意図
    • Docker ネットワーク経由で必ず db コンテナに TCP 接続する
    • 認証情報はすべて環境変数(.env)から取得し、未設定時は起動時エラーで検知
    • データベース名のみフォールバック値を残し、初回セットアップを簡単に

※本記事では本番環境について触れないため、production 変更不要です。

5. dotenv-rails を導入し、.env で環境変数を管理する

  1. dotenv-rails をインストール

    backend/Gemfile に追記し、Rails 起動時に .env の内容を自動で読み込むようにします。

    gem 'dotenv-rails', groups: [:development, :test]
    
  2. 依存をインストール

    cd backendbackend/ ディレクトリに移動してから、以下を実行します。

    docker run --rm \
      -v "$(pwd)":/app \
      -w /app \
      ruby:3.3.6 \
      bash -lc "bundle install"
    
  3. backend/.env ファイルを作成し、Git 管理外に設定

    • backend/.env を作成し、以下を追加

      # backend/.env
      POSTGRES_USER=postgres
      POSTGRES_PASSWORD=very_strong_password       # ← 機密情報
      POSTGRES_DB=backend_development
      
      DATABASE_HOST=db
      DATABASE_USER=postgres
      DATABASE_PASSWORD=very_strong_password       # ← 機密情報
      DATABASE_NAME=backend_development
      
      RAILS_ENV=development
      RACK_ENV=development
      
    • backend/.gitignore に以下を追加

      backend/.env
      

      なぜ .env.gitignore に追加するのか

      • .env にはデータベースのパスワードやユーザー名など、外部に知られてはいけない機密情報が含まれます。
      • これをリポジトリに含めると、公開リポジトリ化した際や誤って他者に共有した際に、機密情報が漏洩してしまうリスクがあります。
      • そのため、実際の値は各開発者・各環境(ステージング/本番)ごとに手元で管理し、サンプルだけをコミットする運用がベストプラクティスです。

    ⚠️ 本番環境では要注意!
    ここに記載した POSTGRES_PASSWORDDATABASE_PASSWORD はあくまでサンプルです。
    実際の構築時には、推測されにくい強力なパスワード(例:英大文字・英小文字・数字・記号を組み合わせた 16 文字以上)を設定し、.env は必ず .gitignore に入れてリポジトリには含めないようにしてください。

6. backend/Dockerfile.dev を作成する

Rails(バックエンド)の開発用 Dockerfile を backend/Dockerfile.dev として新規作成します。

※不要なコメントは削除し、読みやすく整えてください。
※いずれもターミナルで一括作成する例です。エディタでファイルを作成して中身を貼り付けても問題ありません。

cat << 'EOF' > backend/Dockerfile.dev
# ───────────────────────────────────────
# 1. ベースイメージ:Ruby 3.3.6
# ───────────────────────────────────────
FROM ruby:3.3.6

# ───────────────────────────────────────
# 2. システム依存パッケージをインストール
# ───────────────────────────────────────
RUN apt-get update -qq && \
    apt-get install -y nodejs build-essential postgresql-client
# ───────────────────────────────────────
# 3. 作業ディレクトリを設定
# ───────────────────────────────────────
WORKDIR /app

# ───────────────────────────────────────
# 4. RubyGem をインストール
# ───────────────────────────────────────
COPY Gemfile Gemfile.lock ./
RUN bundle install

# ───────────────────────────────────────
# 5. アプリケーションコードをコピー
# ───────────────────────────────────────
COPY . .

# ───────────────────────────────────────
# 6. ポート宣言
# ───────────────────────────────────────
EXPOSE 3000

# ───────────────────────────────────────
# 7. 開発サーバー起動コマンド
# ───────────────────────────────────────
CMD ["bash", "-lc", "bundle exec rails db:create db:migrate && bundle exec rails server -b 0.0.0.0 -p 3000"]

EOF
  • apt-get update && apt-get install で必要なシステム依存パッケージを一度にインストールしています。
  • COPY Gemfile Gemfile.lock ./RUN bundle install の順番でキャッシュを効かせつつ、Gem のインストールを行います。
  • COPY . . 以降はアプリコードを丸ごとコピーし、ホットリロードなど開発中の変更を反映できるようにしています。

7. frontend/Dockerfile.dev を作成する

Next.js(フロントエンド)の開発用 Dockerfile を frontend/Dockerfile.dev として新規作成します。

※不要なコメントは削除し、読みやすく整えてください。
※いずれもターミナルで一括作成する例です。エディタでファイルを作成して中身を貼り付けても問題ありません。

cat << 'EOF' > frontend/Dockerfile.dev
# ───────────────────────────────────────
# 1. ベースイメージ:Node.js 23
# ───────────────────────────────────────
FROM node:23

# ───────────────────────────────────────
# 2. 作業ディレクトリを設定
# ───────────────────────────────────────
WORKDIR /app

# ───────────────────────────────────────
# 3. 依存パッケージをインストール
# ───────────────────────────────────────
COPY package.json package-lock.json ./
RUN npm install

# ───────────────────────────────────────
# 4. アプリケーションコードをコピー
# ───────────────────────────────────────
COPY . .

# ───────────────────────────────────────
# 5. ポート宣言
# ───────────────────────────────────────
EXPOSE 3000

# ───────────────────────────────────────
# 6. 開発サーバー起動コマンド
# ───────────────────────────────────────
CMD ["npm", "run", "dev"]
EOF
  • COPY package.json package-lock.json ./RUN npm installCOPY . . の順で依存をインストールし、パッケージ変更時のみ npm install が再実行されるようキャッシュを活用しています。
  • 最後の CMD ["npm", "run", "dev"] で Next.js の開発サーバーが起動するよう設定

8. docker-compose.yml を作成し、各サービスを定義する

ここまでで「backend/」に Rails 用 Dockerfile.dev、「frontend/」に Next.js 用 Dockerfile.dev が用意できたので、最後にそれらを統合して一括起動する docker-compose.yml を作成します。プロジェクトルートで以下を実行してください。

※下記はターミナルでファイルの中身まで一括作成するコマンドですが、エディタでフォルダを作成し、中身を直接記述しても問題ありません。

※コメントアウトしてる部分は不要なら削除して下さい。

cat << 'EOF' > docker-compose.yml
services:
  db:
    image: postgres:15
    env_file:
      - backend/.env  # ← これを追加
    environment:
      POSTGRES_HOST_AUTH_METHOD: md5
    ports:
      - "5432:5432"
    volumes:
      - db_data:/var/lib/postgresql/data

  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile.dev
    volumes:
      - ./backend:/app
    ports:
      - "3001:3000"
    env_file:
      - backend/.env
    depends_on:
      - db
    command: >
      bash -c "
        until pg_isready -h db -p 5432 -U postgres; do
          echo 'Waiting for PostgreSQL...'
          sleep 2
        done
        echo 'PostgreSQL is ready!'
        bundle exec rails db:create db:migrate
        bundle exec rails server -b 0.0.0.0 -p 3000
      "

  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile.dev
    volumes:
      - ./frontend:/app
    ports:
      - "3000:3000"
    env_file:
      - backend/.env
    depends_on:
      - backend
    command: npm run dev

volumes:
  db_data:
EOF
  • db(PostgreSQL)
    • env_filebackend/.env から POSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_DB を読み込み、environmentPOSTGRES_HOST_AUTH_METHOD を明示的に設定
    • ports: ["5432:5432"] でホストからの接続を受け付け
    • volumes: db_data でデータ永続化
  • backend(Rails API)
    • Dockerfile.dev を使って開発用イメージをビルド
    • volumes: ./backend:/app でソースと .env をコンテナにマウント
    • ports: ["3001:3000"] でホスト 3001 番→コンテナ 3000 番をマッピング
    • env_file により DATABASE_HOST/DATABASE_USER/DATABASE_PASSWORD/DATABASE_NAME.env から注入
    • depends_on: db で DB 起動後に Rails を起動
    • command 内で pg_isready による待機→マイグレーション→Puma サーバー起動
  • frontend(Next.js)
    • Dockerfile.dev を使って開発用イメージをビルド
    • volumes: ./frontend:/app でソースをマウントし、ホットリロードを有効化
    • ports: ["3000:3000"] でホストからの接続を受け付け
    • env_file により同じ .env から必要な環境変数を注入(例:NEXT_PUBLIC_API_URLも同ファイルで管理可能)
    • depends_on: backend で Rails 起動後に Next.js を起動
    • command: npm run dev で開発サーバー起動

この構成により、秘匿情報はすべて backend/.env に集約され、Compose ファイルやソースコードには一切平文が残りません。

9. コンテナを立ち上げる

プロジェクトルート(~/projects/personal/dummy-app)で以下を実行します。

docker compose up --build
  • 初回ビルド
    • backendfrontend の Dockerfile に沿ってイメージがビルドされます。キャッシュが活用されれば以後のビルドは高速化されます。
    • PostgreSQL イメージはダウンロード済みであればスキップされます。
  • コンテナ起動の流れ
    1. db(PostgreSQL)が起動
      • ログに listening on IPv4 address "0.0.0.0", port 5432 のように出力されたら受け付け開始。
    2. backend(Rails API)が起動
      • pg_isready -h db -p 5432 によって db:5432 - accepting connections のログを検知すると次に進む。
      • bundle exec rails db:create db:migrate が実行され、dummy_app_dev(development)および dummy_app_test(test)が作成される。
      • その後、Listening on http://0.0.0.0:3000(Puma)というログが出れば Rails サーバーが稼働中。
    3. frontend(Next.js)が起動
      • > next dev --turbopackready - started server on 0.0.0.0:3000 というログが出れば Next.js の開発サーバーが稼働中。

ログにエラーが出ていなければ、各コンテナは正常に立ち上がっています。

10. 立ち上がった環境をブラウザで確認する

10.1 Rails API(バックエンド)の確認

  1. 別ターミナルを開き、以下を実行します。

    curl -I http://localhost:3001
    
    • HTTP/1.1 200 OK が返ってくれば Rails サーバーは動作中。
    • もし 404 が返ってきても、「サーバー応答が返っている」状態を確認できれば OK。
  2. ブラウザで http://localhost:3001 を開いてみてください。

    • Rails のウェルカムページ(「Ruby on Rails 7.2.x」など)が表示されればバックエンドは正常です。

10.2 Next.js(フロントエンド)の確認

  1. 別ターミナルを開き、以下を実行します。

    curl -I http://localhost:3000
    
    • HTTP/1.1 200 OK が返ってくれば Next.js サーバーは動作中。
  2. ブラウザで http://localhost:3000 を開いてみてください。

    • Next.js の初期画面(「Get started by editing src/app/page.tsx」など)が表示されればフロントエンドは正常です。

11. API とフロントの連携確認サンプル(任意)

もし「Rails API → Next.js」間の通信が正しく機能するかをデモ的に確認したい場合、以下のような最小構成を試してみてください。

11.1 Rails に CORS 設定を追加する

  1. backend/Gemfilerack-cors を追加

    gem 'rack-cors'
    

    rack-cors の説明

    rack-cors は、Rails API 側で「別のオリジン(今回は Next.js の http://localhost:3000)からのリクエストを許可する」ためのミドルウェアです。これを入れることで、ブラウザが発行するクロスオリジンリクエストがブロックされずに Rails 側へ到達するようになります。

  2. bundle install を実行して rack-cors をインストール

    • Gemfile を変更したため、イメージに反映させるには再ビルドが必要です。以下のコマンドで一時的にコンテナを立ち上げ、root 権限でインストールします。

      docker compose run --rm --user root backend bundle install
      
    • 再ビルド後にコンテナを起動すると、rack-cors が使えるようになります。

  3. backend/config/initializers/cors.rb を作成

    以下の内容を新規ファイルとして追加し、Next.js(http://localhost:3000)からのクロスオリジンリクエストを許可します。

    Rails.application.config.middleware.insert_before 0, Rack::Cors do
      allow do
        origins 'http://localhost:3000'
        resource '*',
          headers: :any,
          methods: [:get, :post, :put, :patch, :delete, :options, :head],
          credentials: true
      end
    end
    
  4. frontend/.env.local を作成

    以下の内容を新規ファイルとして追加し

    NEXT_PUBLIC_API_URL=http://localhost:3001
    
  5. イメージを再ビルド・コンテナを起動

    CORS 設定や rack-cors インストール、Dockerfile の変更を反映するために再度ビルド&起動します。

    docker compose down
    docker compose up --build
    

11.2 Rails API に「時刻取得エンドポイント」を作成

  1. backend/app/controllers/api/time_controller.rb を作成し、下記の様に記述します。

    module Api
      class TimeController < ApplicationController
        def index
          render json: { current_time: Time.now.strftime("%Y-%m-%d %H:%M:%S") }
        end
      end
    end
    
  2. ルーティングを追加( backend/config/routes.rb の末尾などに追記)

    Rails.application.routes.draw do
      # ここから
      namespace :api do
        get 'time', to: 'time#index'
      end
      # ここまで追加
    end
    
  3. 再度、コンテナをリビルド・起動

    docker compose up --build
    
  • これにより、GET <http://localhost:3001/api/time> にアクセスすると、JSON でサーバー時刻が返ります。

11.3 Next.js(フロント)で「時刻を fetch して表示」するコードを追加

  1. frontend/src/app/page.tsx を、下記の様に記述します。

    "use client";
    
    import { useEffect, useState } from "react";
    
    export default function Home() {
      const [time, setTime] = useState<string>("");
    
      useEffect(() => {
        fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/time`)
          .then((res) => res.json())
          .then((data) => setTime(data.current_time))
          .catch(() => setTime("Error"));
      }, []);
    
      return (
        <main className="flex flex-col items-center justify-center min-h-screen">
          <h1 className="text-4xl font-bold">Next.js + Rails API</h1>
          <p className="mt-4 text-xl">
            サーバー時刻: {time || "Loading..."}
          </p>
        </main>
      );
    }
    
    • process.env.NEXT_PUBLIC_API_URLdocker-compose.yml にて http://localhost:3001 に設定しているため、ブラウザ(クライアント)から http://localhost:3001/api/time へリクエストが送られます。
  2. ブラウザで http://localhost:3000 を開いてみてください。

    • 「サーバー時刻: 2025-06-06 12:34:56」など、Rails 側で生成した時刻が表示されれば通信成功です。

まとめ

  • Docker コンテナのみで Rails API + Next.js 環境を一気に構築できる

    → ホストに Ruby・Node.js を入れる必要がなく、誰でも再現性の高い開発環境を手に入れられます。

  • Rails は API モード、Next.js は TypeScript+Tailwind+App Router を採用

    → バックエンドとフロントエンドをそれぞれ最新技術で独立して動かし、シンプルかつ拡張性の高い構成を実現。

  • docker-compose で3つのサービス(db/backend/frontend)を一括起動

    docker compose up --build の一発で「DB → Rails → Next.js」の順にビルド&起動され、ログを追うだけで仕組みを把握できます。

  • Rails の database.yml は必ず TCP 接続設定を行う

    → Docker ネットワーク内では UNIX ソケットが使えないため、host: <%= ENV.fetch("DATABASE_HOST") { "db" } %> を明示的に指定することが重要。

  • フロントは NEXT_PUBLIC_API_URL 環境変数で Rails API を参照

    → 開発マシン(ブラウザ)では http://localhost:3001コンテナ間では http://backend:3000 を使う設定です。これにより、ホストにもコンテナにも同じ .env を流し込むだけで両方から通信できます。

この手順をマスターすれば、今後のプロジェクトでも「バックエンドを Rails API」「フロントを Next.js」で分離したモダンな開発環境を簡単に再現できるようになります。ぜひ実践してみてください。

さいごに

最後までお読みいただき、ありがとうございました。
Docker コンテナのみで構築する手順は最初は慣れが必要かもしれませんが、一度設定を整えれば非常に再現性の高い環境になります。ぜひ本手順を参考に、Rails API+Next.js の開発をスムーズに進めてみてください。

それでは、快適な開発ライフをお祈りしています!

Discussion