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 系向けバージョン
開発環境の全体像
- Docker コンテナで Rails 7.2(API モード)+PostgreSQL 15 を動かす
- Docker コンテナで Next.js(TypeScript+Tailwind+App Router)を動かす
- docker-compose で3つ(DB/Rails/Next.js)を一括管理し、一発で起動する
- Rails は
database.ymlを環境変数経由で切り替え、TCP 接続で DB にアクセス - 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.json、tsconfig.json、src/、public/ など、Next.js のひな形が入っています。
4. backend/config/database.yml を TCP 接続+.env 前提に書き換える
既存の developmentとtest を以下の通り置き換えてください。
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)から取得し、未設定時は起動時エラーで検知 - データベース名のみフォールバック値を残し、初回セットアップを簡単に
- Docker ネットワーク経由で必ず
※本記事では本番環境について触れないため、production 変更不要です。
5. dotenv-rails を導入し、.env で環境変数を管理する
-
dotenv-railsをインストールbackend/Gemfileに追記し、Rails 起動時に.envの内容を自動で読み込むようにします。gem 'dotenv-rails', groups: [:development, :test] -
依存をインストール
cd backendでbackend/ディレクトリに移動してから、以下を実行します。docker run --rm \ -v "$(pwd)":/app \ -w /app \ ruby:3.3.6 \ bash -lc "bundle install" -
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_PASSWORDとDATABASE_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 install→COPY . .の順で依存をインストールし、パッケージ変更時のみ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_fileでbackend/.envからPOSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_DBを読み込み、environmentでPOSTGRES_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
-
初回ビルド
-
backendとfrontendの Dockerfile に沿ってイメージがビルドされます。キャッシュが活用されれば以後のビルドは高速化されます。 - PostgreSQL イメージはダウンロード済みであればスキップされます。
-
-
コンテナ起動の流れ
-
db(PostgreSQL)が起動
- ログに
listening on IPv4 address "0.0.0.0", port 5432のように出力されたら受け付け開始。
- ログに
-
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 サーバーが稼働中。
-
-
frontend(Next.js)が起動
-
> next dev --turbopack→ready - started server on 0.0.0.0:3000というログが出れば Next.js の開発サーバーが稼働中。
-
-
db(PostgreSQL)が起動
ログにエラーが出ていなければ、各コンテナは正常に立ち上がっています。
10. 立ち上がった環境をブラウザで確認する
10.1 Rails API(バックエンド)の確認
-
別ターミナルを開き、以下を実行します。
curl -I http://localhost:3001-
HTTP/1.1 200 OKが返ってくれば Rails サーバーは動作中。 - もし 404 が返ってきても、「サーバー応答が返っている」状態を確認できれば OK。
-
-
ブラウザで
http://localhost:3001を開いてみてください。- Rails のウェルカムページ(「Ruby on Rails 7.2.x」など)が表示されればバックエンドは正常です。
10.2 Next.js(フロントエンド)の確認
-
別ターミナルを開き、以下を実行します。
curl -I http://localhost:3000-
HTTP/1.1 200 OKが返ってくれば Next.js サーバーは動作中。
-
-
ブラウザで
http://localhost:3000を開いてみてください。- Next.js の初期画面(「Get started by editing src/app/page.tsx」など)が表示されればフロントエンドは正常です。
11. API とフロントの連携確認サンプル(任意)
もし「Rails API → Next.js」間の通信が正しく機能するかをデモ的に確認したい場合、以下のような最小構成を試してみてください。
11.1 Rails に CORS 設定を追加する
-
backend/Gemfileにrack-corsを追加gem 'rack-cors'rack-cors の説明
rack-corsは、Rails API 側で「別のオリジン(今回は Next.js のhttp://localhost:3000)からのリクエストを許可する」ためのミドルウェアです。これを入れることで、ブラウザが発行するクロスオリジンリクエストがブロックされずに Rails 側へ到達するようになります。 -
bundle installを実行してrack-corsをインストール-
Gemfileを変更したため、イメージに反映させるには再ビルドが必要です。以下のコマンドで一時的にコンテナを立ち上げ、root 権限でインストールします。docker compose run --rm --user root backend bundle install -
再ビルド後にコンテナを起動すると、
rack-corsが使えるようになります。
-
-
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 -
frontend/.env.localを作成以下の内容を新規ファイルとして追加し
NEXT_PUBLIC_API_URL=http://localhost:3001 -
イメージを再ビルド・コンテナを起動
CORS 設定や
rack-corsインストール、Dockerfile の変更を反映するために再度ビルド&起動します。docker compose down docker compose up --build
11.2 Rails API に「時刻取得エンドポイント」を作成
-
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 -
ルーティングを追加(
backend/config/routes.rbの末尾などに追記)Rails.application.routes.draw do # ここから namespace :api do get 'time', to: 'time#index' end # ここまで追加 end -
再度、コンテナをリビルド・起動
docker compose up --build
- これにより、
GET <http://localhost:3001/api/time> にアクセスすると、JSON でサーバー時刻が返ります。
11.3 Next.js(フロント)で「時刻を fetch して表示」するコードを追加
-
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_URLはdocker-compose.ymlにてhttp://localhost:3001に設定しているため、ブラウザ(クライアント)からhttp://localhost:3001/api/timeへリクエストが送られます。
-
-
ブラウザで
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