公開日 2026-08-13

Dockerで「port is already allocated」を解決する(WSL2/Windowsでの原因特定と対処)

Docker Desktop + WSL2 で port is already allocated が出たとき、原文のどこが原因でどこが巻き添えかを読み、掴んでいるコンテナを特定して直すまでを実際の出力で追う。

目次

  1. 1. 検証環境と、この記事が受け取るエラー文字列
  2. 2. ホストポート 8080 を2つのプロジェクトで取り合って再現する
  3. 3. 原文のどこが原因で、どこが巻き添えか
  4. 4. service "app" is not running は原因ではなく結果
  5. 5. 8080 を掴んでいるコンテナを特定する
  6. 6. ホストポートを変えるか、掴んでいる側を止めるか
  7. 対処A: site-b のホストポートを変える
  8. 対処B: 掴んでいる site-a を止める
  9. 7. ports の右側をいくら変えても衝突は消えない
  10. 8. ポートを空けたのに繋がらない——Created で残ったコンテナの罠
  11. 9. 「ポートが使えない」の3系統を原文で見分ける
  12. 10. 片付けと、対処

docker compose up -dport is already allocated で落ち、続けて打った docker compose execservice "app" is not running を返す。この記事では2つのメッセージの関係を原文から読み、ホストポートを掴んでいる相手を特定して直すまでを、Docker Desktop + WSL2 環境の実際の出力で追います。非対象は Linux ネイティブの Docker Engine、Kubernetes、本番向けのポート設計です。

1. 検証環境と、この記事が受け取るエラー文字列

  • Windows 11
  • WSL2(Ubuntu)
  • Docker Desktop 4.55.0(Docker Engine 29.1.3)
  • nginx:1.27-alpine

コマンドは断りがない限り WSL 側のシェルで実行します。Windows 側で実行するものには PowerShell と明記します。

この記事が扱うのは、次の文字列で検索して来た場合です。同じ原因でもコマンドによって前半が変わり、共通するのは末尾の port is already allocated だけになります。

Bind for 0.0.0.0:8080 failed: port is already allocated
service "app" is not running

エラー本文の前半(failed to set up container networking: の部分)は Engine のバージョンで文言が変わります。この記事の原文はすべて Engine 29.1.3 で採取したものなので、手元の出力と前半が違う場合は末尾の port is already allocated で読み合わせてください。

2. ホストポート 8080 を2つのプロジェクトで取り合って再現する

再現に必要なのは、同じホストポートを公開する compose プロジェクト2つだけです。Windows のターミナルで wsl を実行して Ubuntu に入り、作業ディレクトリを作成します。

# Windows側から始める場合のみ実行
# wsl
mkdir -p ~/projects/docker-port-is-already-allocated-demo/site-a
mkdir -p ~/projects/docker-port-is-already-allocated-demo/site-b
cd ~/projects/docker-port-is-already-allocated-demo
code .

site-a/compose.yml を作成します。こちらが先にポートを掴む側です。

services:
  web:
    image: nginx:1.27-alpine
    ports:
      - "8080:80"

site-b/compose.yml を作成します。重ねるのはホスト側のポートだけです。

services:
  app:
    image: nginx:1.27-alpine
    ports:
      - "8080:80"

先に site-a を起動します。

cd ~/projects/docker-port-is-already-allocated-demo/site-a
docker compose up -d

初回はイメージの取得が入ります。取得後の出力が次の6行です。

 Network site-a_default  Creating
 Network site-a_default  Created
 Container site-a-web-1  Creating
 Container site-a-web-1  Created
 Container site-a-web-1  Starting
 Container site-a-web-1  Started

続けて site-b を起動すると衝突します。

cd ~/projects/docker-port-is-already-allocated-demo/site-b
docker compose up -d
 Network site-b_default  Creating
 Network site-b_default  Created
 Container site-b-app-1  Creating
 Container site-b-app-1  Created
 Container site-b-app-1  Starting
Error response from daemon: failed to set up container networking: driver failed programming external connectivity on endpoint site-b-app-1 (5dac0f8c582949c2637d1c2b988137e3567260d8a05602c41aba695197238fc2): Bind for 0.0.0.0:8080 failed: port is already allocated

終了コードは 1 です。docker run で同じ衝突を起こすと、docker: の接頭辞と末尾のヘルプ行が付き、終了コードは 125 になります。

docker: Error response from daemon: failed to set up container networking: driver failed programming external connectivity on endpoint musing_jackson (0440fa512d551909272d17b78e7d13330288f34c1969fade373a522c493a1c80): Bind for 0.0.0.0:8080 failed: port is already allocated

Run 'docker run --help' for more information

docker run -d の場合はコンテナ ID が1行目に出てからエラーが続きます。ID が出ていても、そのコンテナは起動前の状態です。

3. 原文のどこが原因で、どこが巻き添えか

1行のメッセージに4つの情報が入っています。読む順序は末尾からです。

Error response from daemon: failed to set up container networking: driver failed programming external connectivity on endpoint site-b-app-1 (5dac0f8c...): Bind for 0.0.0.0:8080 failed: port is already allocated
  • Bind for 0.0.0.0:8080 failed: port is already allocated — ここだけが原因です。0.0.0.0:8080ホスト側の待ち受けアドレスとポートで、コンテナ側の 80 はこの行に出てきません。compose.yml"8080:80" のうち、問題になっているのは左の数字だけになります
  • endpoint site-b-app-1 — 名指しされているのは起動に失敗した側です。8080 を掴んでいる相手はこの行に出てきません。掴んでいる側の名前は5章の docker ps --filter publish で出します
  • (5dac0f8c...) — endpoint ID で、起動のたびに変わります。検索エンジンに貼るときは削ってください
  • failed to set up container networking: / driver failed programming external connectivity — bind 失敗を包んだ結果側の説明で、独立した原因ではありません

先頭の 0.0.0.0 は「すべてのインターフェースで待ち受ける」指定ですが、これは自分の compose.yml の写しではありません。site-b"127.0.0.1:8080:80" とホスト側 IP 付きに書き換えて衝突させても、原文の IP は変わりませんでした。

Error response from daemon: failed to set up container networking: driver failed programming external connectivity on endpoint site-b-app-1 (95071a2b5e852f88eb44d226a4af7a02365081bd15446e99b884d5dc5ef04147): Bind for 0.0.0.0:8080 failed: port is already allocated

掴んでいる site-a 側が 0.0.0.0 で公開している以上、ループバックだけに絞っても衝突は避けられません。照合に使えるのはポート番号だけです。

4. service "app" is not running は原因ではなく結果

起動に失敗したあと、そのまま次のコマンドを打つと別のメッセージに変わります。

docker compose exec app nginx -v
service "app" is not running

このメッセージは「ポートが埋まっている」とは一言も言いません。しかも docker compose ps が返すのは見出しだけです。

docker compose ps
NAME      IMAGE     COMMAND   SERVICE   CREATED   STATUS    PORTS

ログも空のまま終了コード 0 で返ります。コンテナのプロセスが一度も起動しておらず、出力が存在しないためです。

docker compose logs app

コンテナ自体は消えたわけではなく、-a を付けると Created で残っているのが見えます。

docker compose ps -a
NAME           IMAGE               COMMAND                  SERVICE   CREATED        STATUS    PORTS
site-b-app-1   nginx:1.27-alpine   "/docker-entrypoint.…"   app       1 second ago   Created

失敗の原文が残っているのは、このコンテナの中です。up のスクロールを流してしまっても、ここから読み直せます。

docker inspect site-b-app-1 --format '{{.State.Status}} / {{.State.Error}}'
created / failed to set up container networking: driver failed programming external connectivity on endpoint site-b-app-1 (6e31cce94f99a8986f7dcd962e03f5c309656a5e0c179bb1250f4d07add87fbf): Bind for 0.0.0.0:8080 failed: port is already allocated

直す順序はポート側が先です。service "app" is not running が報告しているのは「コンテナが起動していない」という状態で、起動を止めているのは 8080 の bind 失敗のほうにあります。このメッセージが消えるのは、ポートが空いた時点です。

なお、up の側にポート衝突の原文が無く no space left on device が出ている場合は、起動を止めている原因が別にあります。Docker のデータ領域が満杯だとコンテナを作成できず、その後の exec が同じ service "app" is not running を返すためです。その切り分けは Dockerで「no space left on device」を解決する(WSL2でDockerのディスクとマウント先を見分ける) にあります。

5. 8080 を掴んでいるコンテナを特定する

公開ポートで絞り込むと、掴んでいる側が1行で出ます。

docker ps --filter publish=8080
CONTAINER ID   IMAGE               COMMAND                  CREATED          STATUS          PORTS                                     NAMES
a99315b4195a   nginx:1.27-alpine   "/docker-entrypoint.…"   27 seconds ago   Up 26 seconds   0.0.0.0:8080->80/tcp, [::]:8080->80/tcp   site-a-web-1

NAMESsite-a-web-1 が相手で、PORTS0.0.0.0:8080->80/tcp が原文の 0.0.0.0:8080 と一致する。この一致が取れれば、掴んでいる側の特定はここで終わりです。

一方、OS 側のコマンドからコンテナ名へは辿り着けません。Windows の PowerShell で見ると、待ち受けているのは Docker Desktop 本体です。

> netstat -ano | findstr :8080
  TCP    0.0.0.0:8080           0.0.0.0:0              LISTENING       16776
  TCP    [::]:8080              [::]:0                 LISTENING       16776
  TCP    [::1]:8080             [::]:0                 LISTENING       15556
> tasklist /FI "PID eq 16776"

Image Name                     PID Session Name        Session#    Mem Usage
========================= ======== ================ =========== ============
com.docker.backend.exe       16776 Console                    2    225,708 K

PID 16776 は com.docker.backend.exe、つまり Docker Desktop のバックエンドです。この PID を taskkill しないでください。 Docker Desktop ごと止まり、掴んでいるコンテナは残ります([::1] 側の 15556 は wslrelay.exe で、これも WSL の中継プロセスです)。

WSL 側から ss で見た場合も、Docker が公開したポートについては Process 列が空になります。

$ ss -ltnp 'sport = :8080'
State  Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0      4096               *:8080            *:*

同じ WSL の中で自分が起動したプロセスなら、Process 列に名前と PID が出ます。

$ ss -ltnp 'sport = :9099'
State  Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0      5            0.0.0.0:9099      0.0.0.0:*    users:(("python3",pid=13857,fd=3))

ホストのポートを自分のプロセスで代理受けしてからコンテナへ転送するのが、Docker Desktop の仕組みです。そのため netstatss が名指しできるのは代理側までで、コンテナ名は docker ps からしか出ません。netstat の出番は、docker ps --filter publish=8080 が空だったとき(9章)です。

なお、WSL の中で動いている自分のプロセスが同じ番号を使っていても、この衝突は起きません。0.0.0.0:9099 を待ち受ける python3 を動かしたまま docker run -p 9099:80 を実行しても、起動は成功です。Docker Desktop の Docker Engine は別のディストリビューションで動くため、ポートの取り合いになりません。port is already allocated を見て WSL 側のプロセスを探すのは空振りです。

6. ホストポートを変えるか、掴んでいる側を止めるか

どちらでも直ります。site-a が動き続ける必要があるかどうかで選んでください。

対処A: site-b のホストポートを変える

site-b/compose.yml を更新し、左の数字だけ 8081 にします。

services:
  app:
    image: nginx:1.27-alpine
    ports:
      - "8081:80"

site-b で起動し直します。

cd ~/projects/docker-port-is-already-allocated-demo/site-b
docker compose up -d
 Container site-b-app-1  Recreate
 Container site-b-app-1  Recreated
 Container site-b-app-1  Starting
 Container site-b-app-1  Started

表示された Recreated は、compose.yml の変更で設定が変わり、失敗した残骸が作り直されたことを表す行です。公開ポートを確認します。

docker compose ps
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8081/
NAME           IMAGE               COMMAND                  SERVICE   CREATED         STATUS         PORTS
site-b-app-1   nginx:1.27-alpine   "/docker-entrypoint.…"   app       3 seconds ago   Up 2 seconds   0.0.0.0:8081->80/tcp, [::]:8081->80/tcp
200

合格条件は、PORTS0.0.0.0:8081->80/tcpcurl200 の2つです。

対処B: 掴んでいる site-a を止める

site-a のほうが不要なら、そちらを停止します。

cd ~/projects/docker-port-is-already-allocated-demo/site-a
docker compose stop
 Container site-a-web-1  Stopping
 Container site-a-web-1  Stopped

停止したコンテナはポートを掴みません。docker ps --filter publish=8080 が空になったのを確認してから、site-b を起動し直してください。

cd ~/projects/docker-port-is-already-allocated-demo/site-b
docker compose down
docker compose up -d

down を挟む理由は8章です。compose.yml を書き換えない対処Bでは、up -d だけでは残骸が作り直されません。

7. ports の右側をいくら変えても衝突は消えない

ポートを変える対処で結果を分けるのは、"8080:80" のどちらを書き換えるかです。右側だけを変えても、同じエラーが同じ数字で出続けます。

services:
  app:
    image: nginx:1.27-alpine
    ports:
      - "8080:8081"
Error response from daemon: failed to set up container networking: driver failed programming external connectivity on endpoint site-b-app-1 (4ce1e9811d9068c3bbb77f71a43cf1be16df53f0fb9e7d4f66b740c9810fcaa7): Bind for 0.0.0.0:8080 failed: port is already allocated

原文の 0.0.0.0:8080 が変わっていません。右側はコンテナの中で待ち受けているポートを指すので、ホスト側の奪い合いには関係しないためです。

左右をまとめて入れ替えると、今度はエラーを出さずに壊れます。"8081:8080" にすると起動は成功し、docker compose ps の見た目も正常です。

NAME           IMAGE               COMMAND                  SERVICE   CREATED         STATUS         PORTS
site-b-app-1   nginx:1.27-alpine   "/docker-entrypoint.…"   app       3 seconds ago   Up 2 seconds   0.0.0.0:8081->8080/tcp, [::]:8081->8080/tcp

しかし転送先の 8080 では nginx が待っていないため、接続だけが失敗します。

$ curl -v http://localhost:8081/
*   Trying [::1]:8081...
* Connected to localhost (::1) port 8081
> GET / HTTP/1.1
> Host: localhost:8081
> User-Agent: curl/8.5.0
> Accept: */*
>
* Recv failure: Connection reset by peer
* Closing connection

curl の終了コードは 56 です。左右の意味は Docker初心者向け: compose.yml の中身を1項目ずつ読む入門ports の節で扱っています。

注意したいのは、ports の指定が正しくても Recv failure: Connection reset by peer が出る点です。コンテナ内のアプリが 127.0.0.1 で待っている場合と、アプリが起動していない場合が同じ文言になり、docker compose ps の表示でも区別できません。3つの見分け方は Dockerで「localhostに繋がらない」を解決する(WSL2でコンテナの待受アドレスを見分ける) にあります。

8. ポートを空けたのに繋がらない——Created で残ったコンテナの罠

落とし穴があるのは、対処Bを選んだ場合だけです。site-a を止めて 8080 を空け、site-bup -d を打ち直すと、Compose は成功したように見えます。

 Container site-b-app-1  Starting
 Container site-b-app-1  Started

終了コードは 0 です。ところが docker compose psPORTS を見ると、公開ポートが消えています。

NAME           IMAGE               COMMAND                  SERVICE   CREATED         STATUS         PORTS
site-b-app-1   nginx:1.27-alpine   "/docker-entrypoint.…"   app       7 seconds ago   Up 2 seconds   80/tcp

80/tcp はコンテナ側の公開表示だけで、ホストへの割り当てが付いていない形です。docker port は何も返さず、接続も通りません。

$ docker port site-b-app-1
$ curl http://localhost:8080/
curl: (7) Failed to connect to localhost port 8080 after 0 ms: Couldn't connect to server

docker compose restart でも同じ結果になり、しかもこちらは 8080 がまだ埋まっている状態でさえ Started と表示して終了コード 0 を返します。

inspect で見えるのは、設定としてのポート指定は残っているのに、実際の割り当てとネットワーク接続だけが空になっている状態です。

docker inspect site-b-app-1 --format '{{json .HostConfig.PortBindings}}'
docker inspect site-b-app-1 --format '{{json .NetworkSettings.Ports}}'
docker inspect site-b-app-1 --format '{{json .NetworkSettings.Networks}}'
{"80/tcp":[{"HostIp":"","HostPort":"8080"}]}
{"80/tcp":[]}
{}

最後の {} は、このコンテナがどのネットワークにも繋がっていないことを示します。正常なコンテナで同じ場所を見ると、プロジェクトのネットワーク名と IP アドレスが入った JSON です(長いので抜粋)。

{"site-a_default":{ ... ,"Gateway":"172.21.0.1","IPAddress":"172.21.0.2", ... }}

影響は公開ポートだけにとどまりません。ネットワーク未接続のまま Up になっているため、コンテナからの外向き通信も落ちます。

docker exec site-b-app-1 wget -qO- --timeout=3 http://example.com
wget: bad address 'example.com'

コンテナの中では nginx が正常に応答するので、プロセスの起動そのものは成功です。

docker exec site-b-app-1 wget -qO- --timeout=3 http://127.0.0.1/
<!DOCTYPE html>
<html>
<head>

この検証では、startrestartNetworkSettings.Networks{} のままにし、作り直したときだけ復旧しました。created で残ったコンテナを起こす経路でネットワークの再接続が行われていないことになりますが、Engine 内部の実装まで確かめたわけではないので、ここでは観測された範囲にとどめます。復旧の手順は作り直しです。

docker compose down
docker compose up -d

down を避けたい場合の代替が --force-recreate です。

docker compose up -d --force-recreate
 Container site-b-app-1  Recreated
 Container site-b-app-1  Starting
 Container site-b-app-1  Started

どちらの場合も、docker compose psPORTS0.0.0.0:8080->80/tcp が戻っていることを確認してください。Up という表示は、公開ポートが有効であることを保証しません。

対処A(compose.yml のポートを書き換える)でこの問題が起きないのは、設定が変わると Compose が自動的に Recreated へ倒すからです。書き換えずにポートを空けた対処Bだけが、残骸をそのまま起こしにいきます。

9. 「ポートが使えない」の3系統を原文で見分ける

docker ps --filter publish=<ポート> が空なら、原因はコンテナの衝突ではありません。Docker Desktop 環境では、原因ごとにメッセージが別物です。

flowchart TD
    A["起動が失敗した"] --> B{"メッセージの末尾"}
    B -->|"port is already allocated"| F["docker ps --filter publish で相手を出す"]
    B -->|"Only one usage of each socket address"| G["netstat -ano で PID を引く"]
    B -->|"forwards/expose returned unexpected status 500"| H["netsh で確保済み範囲を確認する"]
    F --> I{"該当するコンテナは"}
    I -->|"出た"| C["そのコンテナが掴んでいる"]
    I -->|"空だった"| J["この3例では確定できない"]
    G --> D["Windows 側のプロセスが掴んでいる"]
    H --> E["Windows の予約ポート範囲に入っている"]

port is already allocated の枝で docker ps --filter publish が空だった場合、この記事で再現した3例には当てはまりません。疑う範囲は、Docker Desktop の再起動、docker context の切り替わり、別の Docker 環境の併用などです。

Windows 側のプロセスが掴んでいる場合は、port is already allocated にはなりません。Docker 以外のプロセスに 8083 を握らせた状態で docker run -d -p 8083:80 を打つと、Windows のソケットエラーがそのまま出ます。

docker: Error response from daemon: ports are not available: exposing port TCP 0.0.0.0:8083 -> 127.0.0.1:0: listen tcp 0.0.0.0:8083: bind: Only one usage of each socket address (protocol/network address/port) is normally permitted.

netstat -ano が本来の使い方になるのは、この系統だけです。出てきた PID が Docker Desktop 以外なら、そのプロセスを止めるか、ホストポートをずらしてください。

予約ポート範囲に入っている場合の文言は、さらに別物です。Hyper-V や WSL が起動時に確保した範囲へ -p で入り込むと、誰も待ち受けていないのに bind できません。

docker: Error response from daemon: ports are not available: exposing port TCP 0.0.0.0:49900 -> 127.0.0.1:0: /forwards/expose returned unexpected status: 500

このとき 49900 を LISTEN しているプロセスは存在せず、netstat -ano | findstr :49900 の結果も空です。確保済みの範囲は PowerShell で確認できます。

> netsh interface ipv4 show excludedportrange protocol=tcp

Protocol tcp Port Exclusion Ranges

Start Port    End Port
----------    --------
     49894       49993
     50000       50059     *
     50060       50159
     50160       50259
     50260       50359
     50460       50559
     61870       61969

* - Administered port exclusions.

49900 が入るのは先頭の 49894-49993 です。ただし表に出た範囲がすべて等しく塞がるわけではなく、* 付きの 50000-50059 に入る 50000 では同じ手順がそのまま通りました。範囲の一覧は再起動のたびに変わるため、この記事の数字ではなく手元の出力と突き合わせてください。今回確保されていたのはいずれも 49894 以上で、80808081 のような番号は含まれていません。順序としては、ports のホスト側に動的ポート域の番号を選んだうえで ports are not available が出たとき、はじめてこの表を見る形です。

10. 片付けと、対処

検証用のコンテナとネットワークを削除します。このデモの2プロジェクトはどちらも nginx を起動するだけなので、消えて困るものはありません。

cd ~/projects/docker-port-is-already-allocated-demo/site-a
docker compose down
cd ~/projects/docker-port-is-already-allocated-demo/site-b
docker compose down

8章の down--force-recreate を自分の構成へ読み替えるときは、先にデータの置き場所を確認してください。-v を付けなければ named volume は残りますが、コンテナの中だけに書いたデータは作り直しで消えます。

症状ごとの対処は次の6件です。

  • 原文の数字と名前の対応が分からない → 3章
  • service "app" is not running しか出ていない → 4章(先に直すのはポート側)
  • 掴んでいる相手が分からない → 5章
  • ポートを変えたのに同じエラーが出る → 7章(左の数字を変える)
  • ポートを空けたのに繋がらない → 8章(down してから up -d
  • docker ps --filter publish が空 → 9章

compose.yml の各キーの読み方は Docker初心者向け: compose.yml の中身を1項目ずつ読む入門、イメージ側の設定と起動設定の切り分けは Docker初心者向け: compose.yml と Dockerfile の違いを最小構成で理解する にあります。

バインドマウント越しにファイルを編集できなくなる症状は Dockerで「Permission denied」を解決する(WSL2のバインドマウントとUIDの食い違い) で扱っています。

シリーズ 1/4

このシリーズ

Dockerのエラーを原文から切り分ける

  1. 1. Dockerで「port is already allocated」を解決する(WSL2/Windowsでの原因特定と対処) 現在の記事
  2. 2. Dockerで「Permission denied」を解決する(WSL2のバインドマウントとUIDの食い違い)
  3. 3. Dockerで「localhostに繋がらない」を解決する(WSL2でコンテナの待受アドレスを見分ける)
  4. 4. Dockerで「no space left on device」を解決する(WSL2でDockerのディスクとマウント先を見分ける)