Docker初心者のためのNext.js+FastAPI開発環境構築ガイド

# 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

```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

```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**

```yaml
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**

```typescript
'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**

```python
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）

```yaml
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）とコンテナの間でファイルを共有する仕組み**

#### 書き方の違いによる意味の変化

```yaml
volumes:
  - ./frontend:/app          # ホスト:コンテナ → 双方向同期
  - /app/node_modules        # コンテナのみ → 同期から除外
```

**重要なポイント:**
- `:` （コロン）があるかないかで意味が変わる

#### 詳しい解説

##### パターン1: ホスト連携（:あり）
```yaml
- ./frontend:/app
  ↑ホスト側  ↑コンテナ側
```
**意味:** 
- あなたのPCの `./frontend` フォルダとコンテナの `/app` フォルダを **双方向で同期**
- ファイル変更が即座に反映される

##### パターン2: 除外・独立（:なし）
```yaml
- /app/node_modules
  ↑コンテナ側のみ
```
**意味:**
- コンテナの `/app/node_modules` だけを指定
- ホスト側とは **連携しない**、コンテナ内で **独立して管理**

#### 処理の順序

```yaml
volumes:
  - ./frontend:/app          # ① まず全体を同期
  - /app/node_modules        # ② その後、node_modulesだけ除外
```

**Dockerの処理順序:**
1. **第1段階:** `./frontend` の全内容を `/app` に同期
2. **第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 の実際の効果

```bash
# volumes設定により実現される開発フロー
1. VSCodeでpage.tsxを編集
2. 瞬時にコンテナに反映される（./frontend:/app）
3. でもnode_modulesはコンテナ専用のまま（/app/node_modules）
4. ブラウザで変更確認 ✅
```

#### 本番環境（docker-compose.prod.yml）

```yaml
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なし
```

### なぜこの分け方が重要なのか

#### ❌ 間違った分け方
```bash
# 開発環境でPython 3.10、本番でPython 3.11を使う
❌ 「バージョン違いで動かない！」
```

#### ✅ 正しい分け方
```bash
# 同じPython 3.11を使い、起動オプションだけ変える
✅ 「同じ環境で、設定だけ最適化」
```

### 開発環境での実行

```bash
# 開発環境で起動
docker-compose up

# 本番設定で起動（テスト用）
docker-compose -f docker-compose.prod.yml up
```

### 一致性確保の具体例

```dockerfile
# 共通の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%同一
- 環境変数で動作モードを切り替え

## 動作確認とトラブルシューティング

### 基本的な動作確認

1. **アプリケーション起動**
   ```bash
   docker-compose up
   ```

2. **アクセス確認**
   - フロントエンド: `http://localhost:3000`
   - バックエンドAPI: `http://localhost:8000`
   - APIドキュメント: `http://localhost:8000/docs`

3. **動作テスト**
   - ボタンをクリック
   - 「Hello from FastAPI! Dockerコンテナで動作中です！」が表示される

### よくあるエラーと解決方法

#### 1. ポートが既に使用されている
```bash
Error: Port 3000 is already in use
```
**解決方法：**
```bash
# 使用中のプロセスを確認
lsof -i :3000

# プロセスを終了またはポート番号を変更
```

#### 2. イメージのビルドエラー
```bash
# キャッシュをクリアして再ビルド
docker-compose build --no-cache
```

#### 3. コンテナの状態確認
```bash
# 動作中のコンテナを確認
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は開発・運用の効率化と安定性向上のためのツール」**だということです。

### 重要なポイント
1. **Dockerfile**: 環境構築手順の標準化
2. **Docker Compose**: 複数サービスの統合管理
3. **イメージの再利用**: 開発から本番まで一貫した環境
4. **トラブルシューティング**: ログとコンテナ状態の確認方法

### 次のステップ
- マルチステージビルドによるイメージサイズ最適化
- 環境変数を使った設定管理の改善
- データベースコンテナの追加
- CI/CDパイプラインとの連携