3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

Rails APIモードとNext.jsでMVPまでローカル開発した後に、途中からDocker環境へ移行することに挑戦しました。

本来であれば、最初からDockerの使用を決めて開発をスタートするのがベストプラクティスかもしれません。

しかし、技術のキャッチアップ状況や、実際に開発を進めてみて初めて見えてくる景色もあります。

備忘録も兼ねて、

「これから個人開発を始めようとしている方」
「ローカル開発からDockerへの移行・環境構築に悩んでいる方」
「途中からDockerを導入するとどう開発が進むのか知りたい方」

そんな方々の参考になれば幸いです。

前提条件

開発環境・使用技術

  • マシン: MacBook M5 (Apple Silicon)
  • バックエンド: Ruby 4.0.1 / Rails 8.1.3 (APIモード, PostgreSQL指定)
  • フロントエンド: Next.js 16.2.3 (TypeScript / React)
  • データベース: PostgreSQL 18
  • その他ツール: RuboCop, RSpec, Biome 導入済み
  • 認証方式: JWT認証

ポート番号の設計

  • フロントエンド (Next.js): 3000
  • バックエンド (Rails): 3001

※ Dockerコンテナ内での、バックエンドポート番号は、3000です。

💡 移行前の状態
ローカル環境にて、バックエンドは rails new back --api -T -d postgresql、フロントエンドは npx create-next-app@latest でプロジェクトを作成し、すでに主要機能の検証(MVP)を完了している状態からスタートしました。

最終的なフォルダ構成

ルートディレクトリに compose.yml を配置し、フロントとバックをそれぞれ内包するマルチコンテナ構成を目指します。

.
├── compose.yml
├── back/
│   └── Dockerfile.dev
└── front/
    └── Dockerfile

実行手順

各種設定ファイルの作成

バックエンド(back)の設定

Rails 8で自動生成される本番用の Dockerfile はそのまま残し、開発用として back/Dockerfile.dev を新規作成します。

back/Dockerfile.dev
FROM ruby:4.0.1

ENV LANG=C.UTF-8
ENV TZ=Asia/Tokyo

RUN apt-get update -qq && \
    apt-get install -y \
    build-essential \
    libpq-dev \
    postgresql-client

WORKDIR /app

COPY Gemfile Gemfile.lock ./

RUN gem install bundler
RUN bundle install

COPY . .

EXPOSE 3000

CMD ["rails", "server", "-b", "0.0.0.0"]

フロントエンド(front)の設定

フロントエンド直下に front/Dockerfile を作成します。

front/Dockerfile
FROM node:26

WORKDIR /app

COPY package.json package-lock.json ./

RUN npm install

COPY . .

EXPOSE 3000

CMD ["npm", "run", "dev"]

ルートディレクトリ(compose.yml)の設定

プロジェクトのルート直下にマルチコンテナを管理する compose.yml を作成します。

compose.yml
services:
  db:
    image: postgres:18
    environment:
      POSTGRES_DB: app_development
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
    ports:
      - "5432:5432"
    volumes:
      - db_data:/var/lib/postgresql

  back:
    build:
      context: ./back
      dockerfile: Dockerfile.dev
    volumes:
      - ./back:/app
    ports:
      - "3001:3000"
    depends_on:
      - db
    command: bundle exec rails server -b '0.0.0.0'
    tty: true
    stdin_open: true
    environment:
      - RAILS_ENV=development
  front:
    build:
      context: ./front
      dockerfile: Dockerfile
    volumes:
      - ./front:/app
      - /app/node_modules
    ports:
      - "3000:3000"
    env_file:
      - front/.env
    depends_on:
      - back
    command: sh -c "npm install && npm run dev"

volumes:
  db_data:

Railsのデータベース接続設定

back/config/database.yml を修正し、接続先ホストをDockerコンテナのサービス名(db)に変更します。

back/config/database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  max_connections: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  host: db
  username: postgres
  password: password

development:
  <<: *default
  database: app_development

test:
  <<: *default
  database: app_test

production:
  primary: &primary_production
    <<: *default
    database: back_production
    username: back
    password: <%= ENV["BACK_DATABASE_PASSWORD"] %>
  cache:
    <<: *primary_production
    database: back_production_cache
    migrations_paths: db/cache_migrate
  queue:
    <<: *primary_production
    database: back_production_queue
    migrations_paths: db/queue_migrate
  cable:
    <<: *primary_production
    database: back_production_cable
    migrations_paths: db/cable_migrate

コマンドの実行とDBセットアップ

設定が完了したら、ルートディレクトリでコンテナをビルド・起動します。

# コンテナのビルド
docker compose build

# コンテナの起動(バックグラウンドで起動する場合は -d を付与)
docker compose up

コンテナ内へのアクセス方法

デバッグやコマンド実行の際、必要に応じて各コンテナに入ることができます。

# バックエンドコンテナに入る
docker compose exec back bash

# フロントエンドコンテナに入る
docker compose exec front bash

# データベースコンテナに入る
docker compose exec db bash

# コンテナから抜けるとき
exit

データベースの初期化

コンテナを立ち上げただけではデータベースが存在しないためエラーになります。
初回のみ以下のコマンドでDBの作成とマイグレーションを実行します。

# backコンテナに入って実行する場合
docker compose exec back bash
bin/rails db:create
bin/rails db:migrate
bin/rails db:seed # 必要に応じて

# または、コンテナに入らず直接1行で実行することも可能
docker compose exec back bin/rails db:create db:migrate

遭遇したエラーと対処法

ここからは、ローカルからDocker環境へ移行するにあたって、実際に私が直面したエラーと解決するまでの泥臭いプロセスをまとめています。

PostgreSQL 18系のマウントパス仕様変更エラー

docker compose up を実行した際、db コンテナだけが以下のログを吐いて落ちてしまいました。

db-1 | Error: in 18+, these Docker images are configured to store database data in a
db-1 | format which is compatible with "pg_ctlcluster" (specifically, using
db-1 | major-version-specific directory names). This better reflects how
db-1 | PostgreSQL itself works, and how upgrades are to be performed.
db-1 |
db-1 | See also https://github.com/docker-library/postgres/pull/1259 db-1 |
db-1 | Counter to that, there appears to be PostgreSQL data in:
db-1 | /var/lib/postgresql/data (unused mount/volume)
db-1 |
db-1 | This is usually the result of upgrading the Docker image without
db-1 | upgrading the underlying database using "pg_upgrade" (which requires both db-1 | versions).
db-1 |
db-1 | The suggested container configuration for 18+ is to place a single mount db-1 | at /var/lib/postgresql which will then place PostgreSQL data in a
db-1 | subdirectory, allowing usage of "pg_upgrade --link" without mount point db-1 | boundary issues.
db-1 |
db-1 | See https://github.com/docker-library/postgres/issues/37 for a (long)
db-1 | discussion around this process, and suggestions for how to do so.
db-1 exited with code 1

原因と対策

PostgreSQL 18公式Dockerイメージではデータディレクトリの運用方針が変更され、従来の /var/lib/postgresql/data ではなく、 /var/lib/postgresql をマウントすることが推奨されるようになったようです。

compose.yml を以下のように修正することで無事に起動しました。

# 修正前
volumes:
  - db_data:/var/lib/postgresql/data

# 修正後
volumes:
  - db_data:/var/lib/postgresql

Dockerの容量不足によるinitdbエラー

容量不足が原因でエラーとなりました。

db-1  | The files belonging to this database system will be owned by user "postgres".
db-1  | This user must also own the server process.
db-1  | 
db-1  | The database cluster will be initialized with locale "en_US.utf8".
db-1  | The default database encoding has accordingly been set to "UTF8".
db-1  | The default text search configuration will be set to "english".
db-1  | 
db-1  | Data page checksums are enabled.
db-1  | initdb: error: could not create directory "/var/lib/postgresql/18/docker/pg_wal": No space left on device
db-1  | initdb: removing contents of data directory "/var/lib/postgresql/18/docker"
db-1  | 
db-1  | fixing permissions on existing directory /var/lib/postgresql/18/docker ... ok
db-1 exited with code 1

No space left on deviceよりDockerの容量が不足しているのがわかります。

対策

docker system df コマンドでストレージの使用状況を確認したところ、過去のビルドキャッシュや未使用のボリューム(Local Volumes:95%)がディスクを圧迫していました。

TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          41        5         16.57GB   10.65GB (64%)
Containers      5         2         540B      6B (1%)
Local Volumes   298       3         30.98GB   29.62GB (95%)
Build Cache     138       0         12.75GB   3.04GB

以下のクリーンアップコマンドを実行し、不要な領域を解放することで解消しました。

# 未使用のコンテナ、イメージ、ネットワークの削除
docker system prune -a

# 未使用のボリュームの削除(古いデータが消えるので注意)
docker volume prune

Next.jsの型定義・モジュール未認識エラー

Docker環境作成後、フロントエンドで以下のような node_modules 起因のエラーが多発しました。

  • 名前 'process' が見つかりません。ノードの型定義をインストールする必要がありますか? npm i --save-dev @types/node を試してから、tsconfig の型フィールドに 'node' を追加してみてください。ts(2591)
  • モジュール 'react' またはそれに対応する型宣言が見つかりません。ts(2307)
  • この JSX タグにはモジュール パス 'react/jsx-runtime' が存在する必要がありますが、見つかりませんでした。適切なパッケージの種類がインストールされていることを確認してください。ts(2875)

原因と対策

ローカル環境の node_modules と、Dockerコンテナ内の環境で依存関係が正しく同期、またはインストールされていなかったことが原因でした。

compose.yml の front サービスの起動コマンドに npm install を明示的に挟むことで、コンテナ起動時に確実に最新のパッケージがインストールされるように修正しました。

compose.yml
# 修正前
command: npm run dev

# 修正後
command: sh -c "npm install && npm run dev"

サーバー間通信(fetch)時の ECONNREFUSED & HostAuthorization エラー

今回の移行で一番の難所だったのが、Next.jsからRails APIへの通信部分です。

発生した現象

Next.jsのServer Actions(またはSSR)内からの通信で connect ECONNREFUSED 127.0.0.1:3001 が発生。さらにそれを修正しようとすると、Rails側で Blocked hosts: back:3000(HostAuthorizationエラー)が発生しました。

connect ECONNREFUSED 127.0.0.1:3001

front-1 | ⨯ [TypeError: fetch failed] { 
front-1 | [cause]: Error: connect ECONNREFUSED 127.0.0.1:3001 
front-1 | at <unknown> (Error: connect ECONNREFUSED 127.0.0.1:3001) { 
front-1 | errno: -111, 
front-1 | code: 'ECONNREFUSED', 
front-1 | syscall: 'connect', 
front-1 | address: '127.0.0.1', 
front-1 | port: 3001 
front-1 | } 
front-1 | }

Blocked hosts: back:3000(HostAuthorizationエラー)

[ActionDispatch::HostAuthorization::DefaultResponseApp] Blocked hosts: back:3000

問題が発生していたコード

front/app/lib/categories.ts
"use server";

import { getBackendJwt } from "./getBackendJwt";

export default async function fetchCategories() {
	const jwt = await getBackendJwt();

    // ローカル開発時の感覚で「localhost:3001」を指定していた
	const res = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/categories`, {
		headers: {
			Authorization: `Bearer ${jwt}`,
		},
		method: "GET",
		cache: "no-store",
	});

	const categories = await res.json();

	return categories;
}
front/.env
NEXT_PUBLIC_API_BASE_URL=http://localhost:3001/api/v1

解決方法

Dockerコンテナ間における localhost は「自分自身のコンテナ」を指してしまいます。Next.jsコンテナから見た通信相手(Rails)は localhost ではなく、Dockerネットワーク内のサービス名である back になります。

また、今回は "use server"(サーバーサイド処理)からのフェッチだったため、宛先ポートはホスト側(3001)ではなくコンテナ内部のポート(3000)を指定する必要がありました。

フロントエンドの環境変数を修正

front/.env
# 修正前
NEXT_PUBLIC_API_BASE_URL=http://localhost:3001/api/v1

# 修正後(Next.jsサーバーからRailsコンテナへ直接通信させる)
NEXT_PUBLIC_API_BASE_URL=http://back:3000/api/v1

Rails側の許可ホストを設定

Dockerネットワーク経由のホスト名(back)からのリクエストを許可するため、開発環境の設定にホストを追加します。

back/config/environments/development.rb
config.hosts << "back:3000"

設定後にDocker再起動を行います。

おわりに

「Docker環境の構築は一番最初に行うもの」という固定観念がありましたが、MVPまで開発した後からでも十分に整備・移行が可能であると自力で証明できたことは、大きな自信になりました。

また、サーバー間通信のエラーで5日間くらい溶かして、一時は投げ出してやる気がなくなりました。
それでもコンテナのネットワーク構造を一つずつ紐解いたり、別のことを進めたり、Dockerを再起動させたら、思ってない形でエラー解消に辿り着きました。

今回、後付けでDocker環境を整備してみて考えたことですが、「途中からDocker環境整備するのは全然アリ」だと思います。

もちろん、最初から組んでおいたほうが依存関係のズレや余計なネットワークエラーにハマるリスクを減らせるので理想的です。

しかし、「Dockerの勉強に時間を取られて開発が全然進まない……」となってしまうくらいなら、まずはローカルで勢いよくMVPを作り上げ、後からこの記事を参考にDocker化するアプローチも、個人開発の戦略として非常に現実的だと感じました。

この記事が、同じように環境構築で悩む誰かの助けになれば幸いです。もし記述に誤りなどがあれば、コメント等でご教示いただけますと幸いです。

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?