Docker初心者のためのNext.js+FastAPI開発環境構築ガイド
はじめに
プログラミング初心者にとって、「私の環境では動くのに、なぜ他の人のPCでは動かないの?」という経験は誰しもあるのではないでしょうか。この問題を根本的に解決するのがDockerです。
この記事では、Docker完全初心者の私が、Next.js(フロントエンド)とFastAPI(バックエンド)を使った開発環境をDockerで構築する過程で学んだことを、実際のコードとともに解説します。
課題提起:「私の環境では動くのに...」問題
よくある開発現場の問題
- 新メンバー参加時: 「環境構築に3日かかりました...」
- チーム開発: 「私のPCでは動くのに、なぜ?」
- デプロイ時: 「ローカルでは問題なかったのに本番で動かない」
- ライブラリ更新: 「バージョン違いでエラーが...」
これらの問題を解決するのがDockerです。
Dockerの基本概念
Dockerとは何か
Docker = アプリケーションとその実行環境を一つにまとめるツール
重要な用語とその関係
Dockerfile(レシピ)
↓ docker build
イメージ(冷凍食品)
↓ docker run
コンテナ(実際に動いているアプリ)
用語 | 役割 | 実際の例 |
---|---|---|
Dockerfile | 環境構築の手順書 | 「Node.js 18をインストールして、npmでパッケージを入れて...」 |
イメージ | 環境をパッケージ化したもの | アプリ + Node.js + 必要なライブラリが全て含まれた状態 |
コンテナ | イメージから起動した実行環境 | 実際にブラウザでアクセスできる状態 |
Docker Compose | 複数のコンテナを管理 | フロントエンドとバックエンドを同時起動 |
実装:Next.js + FastAPI環境の構築
プロジェクト構造
my-docker-app/
├── docker-compose.yml # 複数サービスの管理
├── .env # 環境変数の設定
├── frontend/
│ ├── Dockerfile # フロントエンド環境の設計書
│ ├── package.json
│ └── src/app/page.tsx
└── backend/
├── Dockerfile # バックエンド環境の設計書
├── main.py
└── requirements.txt
Dockerfileの作成と最適化
フロントエンド(Next.js)のDockerfile
# ベースイメージ:Node.js 18の軽量版
FROM node:18-alpine
# 作業ディレクトリを設定
WORKDIR /app
# package.jsonを先にコピー(依存関係の変更がない限りキャッシュを利用)
COPY package*.json ./
# 依存関係をインストール
RUN npm install
# アプリケーションコードをコピー
COPY . .
# アプリケーションが使用するポートを公開
EXPOSE 3000
# アプリケーションを開発モードで起動
CMD ["npm", "run", "dev"]
最適化のポイント:
-
node:18-alpine
:軽量なLinuxベース -
package*.json
を先にコピー:依存関係が変わらない限りキャッシュを活用 - レイヤーキャッシュにより、ビルド時間を短縮
バックエンド(FastAPI)のDockerfile
# ベースイメージ:Python 3.11の軽量版
FROM python:3.11-slim
# 作業ディレクトリを設定
WORKDIR /app
# requirements.txtを先にコピー
COPY requirements.txt .
# Python依存関係をインストール
RUN pip install -r requirements.txt
# アプリケーションコードをコピー
COPY . .
# アプリケーションが使用するポートを公開
EXPOSE 8000
# FastAPIアプリケーションを起動
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
Docker Composeによる複数サービス構成
docker-compose.yml
version: '3.8'
services:
# フロントエンドサービス
frontend:
build: ./frontend # Dockerfileの場所
ports:
- "${FRONTEND_PORT}:3000" # 環境変数でポート指定
volumes:
- ./frontend:/app # ファイル変更をリアルタイム反映
- /app/node_modules # node_modulesは除外
env_file:
- .env # 環境変数ファイル読み込み
environment:
- NODE_ENV=${NODE_ENV}
- API_URL=${API_URL}
depends_on:
- backend # バックエンドが起動してから開始
logging: # ログ管理設定
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# バックエンドサービス
backend:
build: ./backend
ports:
- "${BACKEND_PORT}:8000" # 環境変数でポート指定
volumes:
- ./backend:/app # ファイル変更をリアルタイム反映
env_file:
- .env # 環境変数ファイル読み込み
environment:
- DEBUG=${DEBUG}
- PYTHONPATH=/app
logging: # ログ管理設定
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
Docker Composeの利点:
- 複数のサービスを一括管理
- サービス間の依存関係を定義
- 開発時のファイル変更をリアルタイム反映
- 環境変数による設定管理
- ログローテーション機能
アプリケーションコード
コンテナ化するアプリケーションを用意するために、
ごくごくシンプルな画面を実装
フロントエンド(Next.js)
frontend/src/app/page.tsx
'use client'
import { useState } from 'react'
export default function Home() {
const [message, setMessage] = useState<string>('')
const [loading, setLoading] = useState<boolean>(false)
const callAPI = async () => {
setLoading(true)
try {
const response = await fetch('http://localhost:8000/hello')
const data = await response.json()
setMessage(data.message)
} catch (error) {
setMessage('エラー:バックエンドAPIに接続できません')
} finally {
setLoading(false)
}
}
return (
<div style={{ padding: '50px', textAlign: 'center' }}>
<h1>Docker環境で動作するWebアプリ</h1>
<button onClick={callAPI} disabled={loading}>
{loading ? '通信中...' : 'バックエンドAPIを呼び出す'}
</button>
{message && <p style={{ marginTop: '20px', fontSize: '18px' }}>{message}</p>}
</div>
)
}
バックエンド(FastAPI)
backend/main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI(title="Docker練習API", version="1.0.0")
# CORS設定:フロントエンドからのアクセスを許可
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_methods=["GET", "POST"],
allow_headers=["*"],
)
@app.get("/hello")
def get_hello_message():
return {
"message": "Hello from FastAPI! Dockerコンテナで動作中です!",
"status": "success"
}
@app.get("/")
def health_check():
return {"status": "API is running in Docker container"}
backend/requirements.txt
fastapi==0.104.1
uvicorn==0.24.0
開発環境と本番環境の一致性確保
共通にするもの vs 分けるもの
Dockerの最大の利点は「アプリケーションコードと基本環境は同じ、設定だけを変える」ことです。
🔄 共通にするもの(重要!)
項目 | 理由 |
---|---|
アプリケーションコード | バグの原因となる差異を防ぐ |
ベースイメージ |
node:18-alpine , python:3.11-slim
|
依存関係 |
package.json , requirements.txt
|
基本的なDockerfile | コンテナの作り方を統一 |
⚙️ 分けるもの(設定のみ)
項目 | 開発環境 | 本番環境 | 理由 |
---|---|---|---|
起動コマンド | npm run dev |
npm run build && npm start |
開発時はホットリロード、本番は最適化 |
ポート公開 | 3000:3000 |
80:3000 |
本番は標準ポート使用 |
ファイル同期 | あり(volumes) | なし | 開発時のみコード変更を反映 |
環境変数 | NODE_ENV=development |
NODE_ENV=production |
デバッグ情報の表示制御 |
ログレベル | 詳細 | エラーのみ | 本番はパフォーマンス重視 |
実際の設定例
開発環境(docker-compose.yml)
version: '3.8'
services:
frontend:
build: ./frontend
ports:
- "3000:3000" # 開発用ポート
volumes:
- ./frontend:/app # ホストの./frontendフォルダ ← → コンテナの/appフォルダを同期
- /app/node_modules # コンテナの/app/node_modulesは同期から除外
environment:
- NODE_ENV=development # 開発モード
command: ["npm", "run", "dev"] # ホットリロード付きで起動
backend:
build: ./backend
ports:
- "8000:8000" # 開発用ポート
volumes:
- ./backend:/app # ホストの./backendフォルダ ← → コンテナの/appフォルダを同期
environment:
- DEBUG=true # デバッグ情報を表示
command: ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
volumes の仕組みを理解する
volumes = ホストPC(あなたのPC)とコンテナの間でファイルを共有する仕組み
書き方の違いによる意味の変化
volumes:
- ./frontend:/app # ホスト:コンテナ → 双方向同期
- /app/node_modules # コンテナのみ → 同期から除外
重要なポイント:
-
:
(コロン)があるかないかで意味が変わる
詳しい解説
パターン1: ホスト連携(:あり)
- ./frontend:/app
↑ホスト側 ↑コンテナ側
意味:
- あなたのPCの
./frontend
フォルダとコンテナの/app
フォルダを 双方向で同期 - ファイル変更が即座に反映される
パターン2: 除外・独立(:なし)
- /app/node_modules
↑コンテナ側のみ
意味:
- コンテナの
/app/node_modules
だけを指定 - ホスト側とは 連携しない、コンテナ内で 独立して管理
処理の順序
volumes:
- ./frontend:/app # ① まず全体を同期
- /app/node_modules # ② その後、node_modulesだけ除外
Dockerの処理順序:
-
第1段階:
./frontend
の全内容を/app
に同期 -
第2段階:
/app/node_modules
だけは同期から外して、コンテナ独自のものを使う
具体例で理解
あなたのPC(ホスト側):
frontend/
├── src/
│ └── page.tsx
├── package.json
└── node_modules/ ← これは重くて問題あり
└── (数万ファイル、Windows用)
volumes設定後のコンテナ内:
/app/
├── src/
│ └── page.tsx ← ホストと同期 ✅
├── package.json ← ホストと同期 ✅
└── node_modules/ ← ホストとは別物、コンテナ独自 ✅
└── (Linux用のファイル)
なぜ node_modules を除外するのか
項目 | ホストのnode_modules | コンテナ独自のnode_modules |
---|---|---|
OS対応 | Windows/Mac用 | Linux用 |
ファイル数 | 数万ファイル | 数万ファイル |
同期速度 | 非常に遅い | 不要(同期しない) |
エラーリスク | OS違いでエラー | 環境に最適化 |
volumes の実際の効果
# volumes設定により実現される開発フロー
1. VSCodeでpage.tsxを編集
2. 瞬時にコンテナに反映される(./frontend:/app)
3. でもnode_modulesはコンテナ専用のまま(/app/node_modules)
4. ブラウザで変更確認 ✅
本番環境(docker-compose.prod.yml)
version: '3.8'
services:
frontend:
build: ./frontend
ports:
- "80:3000" # 本番用標準ポート
# volumes: なし # ファイル同期は不要
environment:
- NODE_ENV=production # 本番モード
command: ["npm", "run", "build", "&&", "npm", "start"] # 最適化ビルド
backend:
build: ./backend
ports:
- "8000:8000"
# volumes: なし # ファイル同期は不要
environment:
- DEBUG=false # デバッグ情報を非表示
command: ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] # reloadなし
なぜこの分け方が重要なのか
❌ 間違った分け方
# 開発環境でPython 3.10、本番でPython 3.11を使う
❌ 「バージョン違いで動かない!」
✅ 正しい分け方
# 同じPython 3.11を使い、起動オプションだけ変える
✅ 「同じ環境で、設定だけ最適化」
開発環境での実行
# 開発環境で起動
docker-compose up
# 本番設定で起動(テスト用)
docker-compose -f docker-compose.prod.yml up
一致性確保の具体例
# 共通のDockerfile(frontend/Dockerfile)
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
# ⚠️ コマンドはdocker-compose.ymlで指定(環境ごとに変更)
# CMD ["npm", "run", "dev"] ← ここは書かない!
重要なポイント:
- Dockerfileは共通、起動方法だけdocker-compose.ymlで変更
- アプリケーションコードは100%同一
- 環境変数で動作モードを切り替え
動作確認とトラブルシューティング
基本的な動作確認
-
アプリケーション起動
docker-compose up
-
アクセス確認
- フロントエンド:
http://localhost:3000
- バックエンドAPI:
http://localhost:8000
- APIドキュメント:
http://localhost:8000/docs
- フロントエンド:
-
動作テスト
- ボタンをクリック
- 「Hello from FastAPI! Dockerコンテナで動作中です!」が表示される
よくあるエラーと解決方法
1. ポートが既に使用されている
Error: Port 3000 is already in use
解決方法:
# 使用中のプロセスを確認
lsof -i :3000
# プロセスを終了またはポート番号を変更
2. イメージのビルドエラー
# キャッシュをクリアして再ビルド
docker-compose build --no-cache
3. コンテナの状態確認
# 動作中のコンテナを確認
docker ps
# ログを確認
docker-compose logs frontend
docker-compose logs backend
Dockerの利点まとめ
1. 環境統一の実現
- 問題: 「私のPCでは動くのに...」
- 解決: 全員が同じDockerイメージを使用
2. 新メンバーの環境構築簡素化
- 従来: Node.js、Python、各種パッケージの個別インストール
-
Docker:
docker-compose up
一つで完了
3. 本番環境との一致性
- 従来: 開発環境と本番環境の差異でトラブル
- Docker: 同じイメージを使用して一貫性を保証
4. スケーラビリティ
- 従来: サーバー追加時の環境構築作業
- Docker: イメージの複製で簡単にスケールアウト
まとめ
Docker初心者として最も重要だった学びは、**「Dockerは開発・運用の効率化と安定性向上のためのツール」**だということです。
重要なポイント
- Dockerfile: 環境構築手順の標準化
- Docker Compose: 複数サービスの統合管理
- イメージの再利用: 開発から本番まで一貫した環境
- トラブルシューティング: ログとコンテナ状態の確認方法
次のステップ
- マルチステージビルドによるイメージサイズ最適化
- 環境変数を使った設定管理の改善
- データベースコンテナの追加
- CI/CDパイプラインとの連携