👁️‍🗨️

Docker + NestJS (WSL) 環境で "Connection reset by peer" と格闘した話

2025/04/01に公開

はじめに

Docker Compose を使用して WSL2 上で NestJS アプリケーションを開発していた際、複数のネットワークエラーに見舞われました。最初はデータベース接続エラー、その後は Connection reset by peer という不可解なネットワークエラーです。この記事では、その原因調査と解決に至るまでのトラブルシューティングを備忘録的に記録していきます。

環境

  • OS: Windows (WSL2 Ubuntu 環境を利用)
  • バックエンド: NestJS
  • データベース: PostgreSQL
  • コンテナ管理: Docker Compose
  • その他: Prisma

発生した問題

1. Prisma データベース認証エラー (P1000)

開発環境を docker compose up で起動したところ、NestJS アプリのログに以下のエラーが出力されました。

PrismaClientInitializationError: Authentication failed against database server at `database`, the provided database credentials for `ユーザー名` are not valid.
...
errorCode: 'P1000'

.env ファイルと compose.yml に設定したデータベースの認証情報が一致していない、またはデータベースユーザーが存在しない可能性が考えられました。

2. "Connection reset by peer"

データベース認証エラーを解決し、NestJS アプリのログには Nest application successfully started と表示されるようになりました。しかし、curl コマンドで動作確認しようとすると、以下のエラーが発生し、アプリケーションに接続できませんでした。

$ curl http://localhost:3000/hello
curl: (56) Recv failure: Connection reset by peer

不思議なことに、このエラーが発生しても NestJS アプリのログには何も出力されませんでした。

調査と試行錯誤

データベース認証エラー (P1000) の解決

  1. 設定ファイルの確認:
  • そもそもの設定ファイルの誤りの可能性があったのでそこから確認します。
  • .envDATABASE_URLcompose.ymldatabase サービスの POSTGRES_USER, POSTGRES_PASSWORD が一致していることを確認。
    -> 一致していた。
  1. データベースボリューム/データの確認と削除:
    • docker compose down -v を試したが、DB ログで Skipping initialization と表示され、ユーザーが作成されていない様子。
    • compose.ymlバインドマウント (./data/db:/var/lib/postgresql/data) を使用していたため、名前付きボリュームではなくホスト側の ./data/db にデータが残っていると推測。
    • sudo rm -rf ./data/db/* で中身を削除
      -> Skipping initializationが表示されてしまう
    • sudo rm -rf ./data/dbディレクトリごと削除 してから docker compose up
      -> これで初期化が実行され、DB ログに Role "choseidoro" does not exist. エラーが出なくなり、認証エラーは解決。

原因

今回のプロジェクトでは、既存のプロジェクトを下敷きに諸々開発した後、古いプロジェクトの文言を軒並み置換していきました。
その際、POSTGRES_USER,POSTGRES _DBなどの環境変数も置換していたため、本来はDBが初期化されていなければいけませんでした。
しかし、バインドマウント先に./data/db/フォルダがあったため、データ有と判断したDocker君は初期化をスキップしてしまい、接続できなくなっていたようでした。
フォルダを削除した結果、データ無と判断したDocker君は、初期化をスキップせず、ユーザー作成など必要な処理をしてくれたのでした。

"Connection reset by peer" の解決

  1. curl での接続確認 (WSL 内部):
  • curl http://localhost:3000/hellocurl http://localhost:3000/health など、ヘルスチェック用のAPIを叩くも、"Connection reset by peer" となり、NestJS ログには変化なし。
  1. NestJS アプリケーションコードの確認:
    • main.ts を確認。bodyParser の明示的な使用が怪しいと考えコメントアウト -> 効果なし。
    • CORS 設定 (origin: true) も確認したが、WSL 内部からの curl では関係なさそう。
  2. ポート競合調査:
    • WSL 内部: ss -tulnp | grep 3000 -> Docker プロセスのみ。問題なし。
    • Windows 側: 管理者権限netstat -ano | findstr "LISTEN" | findstr ":3000" を実行。
        TCP    0.0.0.0:3000           0.0.0.0:0              LISTENING       14732 # Dockerと思われる
        TCP    127.0.0.1:3000         0.0.0.0:0              LISTENING       12360 # 謎のプロセス
        TCP    [::]:3000              [::]:0                 LISTENING       14732 # Dockerと思われる
        TCP    [::1]:3000             [::]:0                 LISTENING       12360 # 謎のプロセス
      
    • タスクマネージャーで PID 12360 を確認 -> wslrelay.exe (WSL の内部プロセス) であることが判明。

原因

Windows 側で wslrelay.exe という WSL の内部プロセスが、何らかの理由で localhost:3000 を確保してしまっていた。これにより、Windows のブラウザや WSL 内部からの curl が本来の Docker コンテナ (NestJS アプリ) に到達できず、接続がリセットされていたと考えられます。

解決策

WSL を完全に再起動することで、ポートを掴んでいた wslrelay.exe プロセスを終了させ、問題を解決しました。

  1. 管理者権限の PowerShell/コマンドプロンプトで wsl --shutdown を実行。
  2. (Docker Desktop を使用している場合) Docker Desktop を再起動。
  3. docker compose up -d でコンテナを起動。
  4. netstat -ano | findstr "LISTEN" | findstr ":3000"wslrelay.exe がポートを掴んでいないことを確認。
  5. curl http://localhost:3000/api/hello で接続確認 -> 成功!

WSL2 環境で Docker を使って開発する場合、Windows 側と WSL 側の両方でポートの競合が発生する可能性があります。特に localhost への接続で問題が発生した場合、netstat コマンドを Windows 側 (管理者権限) で実行し、意図しないプロセス (時には WSL 自体のプロセス) がポートを掴んでいないか確認することが重要です。wsl --shutdown は、WSL 関連のネットワーク問題をリセットするのに有効な手段でした。

まとめ

今回は既存のプロジェクトを下敷きにした新規開発ということもあり、特殊?条件や開発環境の変更タイミングにより、初歩的なはまりポイントにことごとくはまってしまった感がありました。
開発環境系の理解の足りなさを恥じつつも、未来の自分への備忘録としての記事を残しておきます。

おまけ:弊社では一緒に働く仲間を募集しています!

弊社では一緒に働く仲間を募集しています!
株式会社コードユニットは 「エンジニアが自由に挑戦し、成長できる環境を創る」 をビジョンに掲げる札幌のIT企業です。
一緒に働く仲間を募集しているので、もし一緒に働く仲間となってもいいと思った方は、一緒に働く仲間になりましょう!
一緒に働く仲間に興味がある方は、どんな手段でもいいのでお声がけください!
コーポレートサイト

株式会社コードユニット

Discussion