【1時間ハマった】Docker × DynamoDB Local接続エラー完全攻略ガイド

 > **TL;DR**: Docker環境でDynamoDB Localに接続できない？それは**localhostとポート設定**が原因かも！サービス名を使って`http://dynamodb-local:9000`に接続し、イメージを**必ず再ビルド**しよう！

## 🔥 はじめに：私がハマった悪夢のような1時間

「あれ？なんでつながらないんだ...」

シンプルなDocker ComposeでFastAPIとDynamoDB Localを動かそうとしただけなのに、なぜか接続エラーの嵐。公式ドキュメントを見ても解決せず、StackOverflowを彷徨い、数時間を無駄にした経験はありませんか？

**私は1時間もこの問題にハマりました。**

この記事では、多くの開発者が陥る「Docker ComposeでのDynamoDB Local接続エラー」を**完全に解決する方法**をお伝えします。この記事を読めば、あなたは同じ苦しみを味わわずに済むでしょう！

## 💥 発生した問題：謎の「Connection refused」エラー

まず、こんなエラーが出ました：

```bash
# 1回目のエラー
botocore.exceptions.EndpointConnectionError: Could not connect to the endpoint URL: "http://localhost:9000/"

# いろいろ試した後の2回目のエラー
botocore.exceptions.EndpointConnectionError: Could not connect to the endpoint URL: "http://dynamodb-local:8000/"
```

**「おかしい...設定は間違ってないはずなのに...」**

DynamoDBのコンテナは正常に起動しているのに、なぜかバックエンドアプリからの接続だけが失敗する。この状況に頭を抱えました。

## 🔍 原因分析：犯人はこの3つだった！

苦しい試行錯誤の末に発見した問題の本質は以下の3つでした：

1. **😱 Docker環境でのホスト名の勘違い**
   - コンテナ内の「localhost」は外の世界のlocalhostじゃない！
   - 別コンテナへの接続には、サービス名を使う必要がある

2. **😵 ポート設定の混乱**
   - DynamoDBのデフォルトポートは8000なのに9000で設定していた
   - アプリの接続設定とDocker Composeの設定が食い違っていた

3. **🤦‍♂️ Docker Composeの理解不足**
   - `"9000:8000"`の意味は「ホストの9000をコンテナの8000に接続」

**これらの問題が複合的に絡み合って、謎のエラーを引き起こしていたのです！**

## 🏠 「localhost」の罠：コンテナ化で一変する世界

**「ローカル環境ではバッチリ動いていたのに、Dockerに移したら動かない...」**

これが私の最初の困惑でした。コンテナ化する前は、以下のコードで問題なく動作していたのです：

```python
# ローカル環境では問題なく動いていたコード
dynamodb = boto3.resource(
    'dynamodb',
    endpoint_url='http://localhost:9000',  # ローカルでは正しい
    region_name='us-west-2',
    aws_access_key_id='dummy',
    aws_secret_access_key='dummy'
)
```

しかし、**Dockerコンテナに移行した途端**、同じコードが動かなくなりました。なぜでしょうか？

### 「localhost」の意味が変わる！

答えは明快です：**コンテナの世界では「localhost」の意味が変わる**のです。

- **コンテナ化前**：「localhost」はあなたのPC全体を指す
- **コンテナ化後**：「localhost」は**そのコンテナ自身だけ**を指す

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3339625/da70cdeb-9fe6-40aa-9b99-19f17922e324.png)


つまり、FastAPIコンテナ内の「localhost」はFastAPIコンテナ自身を指し、DynamoDBコンテナを指していないのです。これは、**ネットワークの世界観が一変する**ことを意味します。

### 複数コンテナ環境でのlocalhostの混乱

Docker Compose環境では、各コンテナは**独立したネットワーク空間**を持ちます：

```
コンテナA内のlocalhost ≠ コンテナB内のlocalhost ≠ ホストPCのlocalhost
```

そのため、コンテナ間で通信する場合は「localhost」ではなく、**Docker Composeで定義したサービス名**を使う必要があるのです。

### ポートマッピングとlocalhostの二重の罠

私の場合、**localhostの問題**と**ポートマッピングの誤解**が重なり、さらに混乱が深まりました：

```
接続先がlocalhost:9000（間違い）× DynamoDBが8000ポートで動作（勘違い）= 二重の罠
```

## 🔄 ポートマッピングの大誤解：私の初期設定の致命的ミス

問題の核心は**ポートマッピングの誤解**でした。初期の設定では以下のようになっていました：

```yaml
# 🚫 最初の間違った設定
dynamodb-local:
  image: amazon/dynamodb-local
  ports:
    - "9000:8000"  # ホストの9000をコンテナの8000に転送
  command: "-jar DynamoDBLocal.jar -sharedDb -dbPath /home/dynamodblocal/data/"  # ポート指定なし！
```

私は`9000:8000`の設定で「DynamoDBが9000ポートで動いている」と思い込んでいました。しかし実際には：

1. **コンテナ内のDynamoDBは8000番ポート**（デフォルト）で動いていた
2. **ホスト側からは9000番**でアクセスできる状態だった
3. **バックエンドコード**では`http://dynamodb-local:9000`に接続しようとしていた

つまり、**ポートのミスマッチ**が発生！コンテナ内では8000番で待ち受けているのに、9000番に接続しようとしていたのです。

### Docker Composeのポート設定を正しく理解する

Docker Composeの`ports`設定は以下のフォーマットです：

```
"ホスト側のポート:コンテナ側のポート"
```

- **左側**：あなたのPCからアクセスする際のポート番号
- **右側**：コンテナ内のアプリが実際に待ち受けているポート番号

重要なのは、**この設定だけではコンテナ内のアプリのポートは変わらない**ということ。アプリ自体のポートを変更するには、明示的にコマンドオプションで指定する必要があります。

### 解決策：2つのアプローチ

#### アプローチ1: DynamoDBのポートを9000に変更（推奨）
```yaml
# ✅ 推奨解決策
dynamodb-local:
  image: amazon/dynamodb-local
  ports:
    - "9000:9000"  # ホストもコンテナも同じポート
  command: "-jar DynamoDBLocal.jar -sharedDb -port 9000 -dbPath /home/dynamodblocal/data/"  # ここが重要！
```

#### アプローチ2: アプリの接続先を8000に変更
```python
# 別の解決策：バックエンドコードを変える
dynamodb = boto3.resource(
    'dynamodb',
    endpoint_url='http://dynamodb-local:8000',  # 8000ポートに接続
    # 以下略
)
```

どちらの方法でも解決できますが、**全ての設定で同じポート番号を使う**方がシンプルで混乱が少ないため、アプローチ1をおすすめします。

## 📄 FastAPI + DynamoDB Local の完全なdocker-compose.yml例

ここでは、FastAPIとDynamoDB Localを連携させるための完全なdocker-compose.yml例を示します：

```yaml
version: "3"

services:
  # FastAPIバックエンドアプリ
  app:
    container_name: FastAPI
    build: ./backend
    ports:
      - "8000:80"  # ホストの8000をコンテナの80にマッピング
    depends_on:
      - dynamodb-local  # DynamoDBが先に起動するのを保証
    environment:
      - AWS_ACCESS_KEY_ID=dummy
      - AWS_SECRET_ACCESS_KEY=dummy
      - AWS_REGION=us-west-2
      - DYNAMODB_ENDPOINT=http://dynamodb-local:9000  # 重要：サービス名で接続
    
  # フロントエンド（必要に応じて）
  frontend:
    container_name: NextJS
    build: ./frontend
    ports:
      - "3000:3000"
    depends_on:
      - app
  
  # DynamoDB Local
  dynamodb-local:
    image: amazon/dynamodb-local
    container_name: DynamoDB
    ports:
      - "9000:9000"  # ホストの9000をコンテナの9000にマッピング
    command: "-jar DynamoDBLocal.jar -sharedDb -port 9000 -dbPath /home/dynamodblocal/data/"
    volumes:
      - "./dynamodb_data:/home/dynamodblocal/data"  # データの永続化
  
  # DynamoDB管理コンソール（オプション）
  dynamodb-admin:
    image: aaronshaf/dynamodb-admin
    container_name: DynamoAdmin
    ports:
      - "8001:8001"
    environment:
      - DYNAMO_ENDPOINT=http://dynamodb-local:9000
    depends_on:
      - dynamodb-local
```

この設定のポイント：

1. **FastAPIコンテナ**は環境変数`DYNAMODB_ENDPOINT`で接続先を設定
2. **DynamoDB**はポート9000で起動、データは永続化
3. **サービス間の依存関係**を`depends_on`で明示
4. **管理コンソール**も追加（UI操作できて便利）

これをプロジェクト直下に`docker-compose.yml`として保存し、以下のコマンドで起動するだけです：

```bash
docker compose up -d
```

## ✅ 解決方法：これで100%解決！

### 1️⃣ DynamoDBポートを9000に統一する

DynamoDBのポートはデフォルトで8000ですが、9000に変更して統一するのがシンプル：

```yaml
dynamodb-local:
  image: amazon/dynamodb-local
  ports:
    - "9000:9000" # ホストの9000をコンテナの9000にマッピング
  command: "-jar DynamoDBLocal.jar -sharedDb -port 9000 -dbPath /home/dynamodblocal/data/"
  volumes:
    - "./dynamodb_data:/home/dynamodblocal/data"
```

### 2️⃣ アプリケーションの接続設定を修正

```python
# ❌ 間違い：localhostを使っている
dynamodb = boto3.resource(
    'dynamodb',
    endpoint_url='http://localhost:9000',  # これだとコンテナ自身を指してしまう！
    # 以下略
)

# ✅ 正解：サービス名を使う
dynamodb = boto3.resource(
    'dynamodb',
    endpoint_url='http://dynamodb-local:9000',  # Docker Composeのサービス名
    region_name='us-west-2',
    aws_access_key_id='dummy',
    aws_secret_access_key='dummy'
)
```

### 3️⃣ 最も重要！イメージの再ビルド

これを忘れると全ての努力が水の泡に！

```bash
# これが超重要！
docker compose down && docker compose build app && docker compose up -d
```

**「え、こんなことだけ？」** と思われるかもしれませんが、実際にこれだけのことで1時間も悩んでいました。ドキュメントには書かれていない実践的なノウハウです。

## 💡 Docker環境での通信：理解すれば簡単だった

Docker Composeの世界では：

- **localhost** = 「自分自身」というコンテナの島
- **サービス名** = 「別のコンテナ島」への橋

この基本概念を忘れると、必ず接続エラーに悩まされることになります！

## 🔮 よくある間違いトップ3

1. **localhost症候群**: コンテナ間通信なのにlocalhostを使ってしまう
2. **ポート数字だけ合わせ症候群**: 「9000:8000」と設定したのに、両方9000と思い込む
3. **イメージ再ビルド忘れ症候群**: 設定ファイル変更後にイメージを再ビルドしない

## 📚 DynamoDB Localの裏技テクニック

AWS公式ドキュメントには書かれていない便利な設定：

- **-port 9000**: ポート番号をデフォルト8000から変更（他のサービスと衝突回避に便利）
- **-sharedDb**: 一つのDBファイルを共有（開発時のデータ一貫性に重要）
- **-inMemory**: 高速だがデータは永続化されない（テスト時に便利）
- **-dbPath**: データの保存場所を指定（ボリュームマウントと組み合わせて使おう）

## 🎓 学びと教訓

1. **コンテナ間の通信はサービス名**: `localhost`ではなく`サービス名`を使おう
2. **設定は全て一致させる**: アプリとDockerの設定で同じ値を使う
3. **変更後は常に再ビルド**: Dockerイメージは自動更新されない

## 🏁 まとめ：この知識でもう悩まない！

Docker ComposeでDynamoDB Localを使うときは、**ホスト名の指定**と**ポート設定の一貫性**が鍵です。そして最後に必ず**イメージを再ビルド**することを忘れないでください。

この記事のノウハウを実践すれば、私のような「1時間の苦しみ」を経験せずに済みます。今日からあなたもDocker × DynamoDBマスターです！

## 👍 この記事が役に立ったらシェアしてください！

## 📖 参考文献

- [DynamoDB local の使用に関する注意事項 - AWS公式ドキュメント](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/DynamoDBLocal.UsageNotes.html)
- [Docker Compose ネットワーク機能の概要 - Docker公式ドキュメント](https://docs.docker.com/compose/how-tos/networking/)
- [Docker Composeによるサービス定義 - Docker公式ドキュメント](https://docs.docker.com/reference/compose-file/)
- [Networks top-level element - Docker公式ドキュメント](https://docs.docker.com/reference/compose-file/networks/)
- [boto3によるDynamoDB操作 - AWS SDK for Python公式ドキュメント](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb.html)