跳到主要内容
DevOps2026-09-085 分钟阅读

Docker port is already allocated:端口被容器占用的排查与修复

现象:容器起不来,说是端口分不出去

# docker run 时
docker: Error response from daemon: driver failed programming external
connectivity on endpoint ...: Bind for 0.0.0.0:8080 failed: port is already allocated.

# 或更底层的形态
Error starting userland proxy: listen tcp4 0.0.0.0:80: bind: address already in use

一行结论:你要映射到宿主机的那个端口,已经被别人占了。注意主语——不是容器内部端口(那层是 NAT,随便重复),是 -p 冒号左边的宿主机端口。

第一步:分清占坑的是容器还是宿主机进程

# 嫌疑一:别的容器映射了同一端口
docker ps --filter "publish=8080"

# 嫌疑二:宿主机上的普通进程(nginx、本地 dev server……)
sudo ss -tlnp | grep :8080

两条都跑,谁在监听一目了然。处理方式完全不同:

占坑的是容器:

docker ps -a --filter "publish=8080"    # 包括已停止的
docker rm -f <容器名或ID>               # 旧容器删掉,端口释放
docker stop <容器名>                    # 或者只是停掉也释放

占坑的是宿主机进程:要么停掉它(systemctl stop nginx),要么换端口重跑容器。

高频场景一:换了 compose 配置,旧容器还占着端口

compose 以项目名(默认 = 目录名)管理容器。常见的两种撞车:

  1. 改了 docker-compose.yml 的端口想「再起一套」,但旧一套 docker compose up 的容器还在——先 docker compose down;
  2. 两个不同目录恰好同名(比如都叫 app/),compose 项目名相同,互相抢容器、抢端口。用 -p 显式指定项目名隔离:
docker compose -p projectA up -d

高频场景二:容器删了,端口还被占(docker-proxy 残留)

少数情况下 docker ps 里没有占用者,ss 里监听的是 docker-proxy 进程——重启 Docker 服务可清掉残留:

sudo systemctl restart docker

这是最后手段,所有容器会一起重启,生产环境先评估影响。

一个反直觉细节:绑定地址不同,可以共存

docker run -p 127.0.0.1:8080:80 --name a ...   # 只绑回环
docker run -p 192.168.1.5:8080:80 --name b ... # 绑特定网卡

端口冲突的判定是**「地址+端口」组合**,不是单独的端口号。127.0.0.1:8080 和 0.0.0.0:8080 冲突(后者包含前者),但 127.0.0.1:8080 和 192.168.1.5:8080 可以共存。调试时这个细节能省一次「明明没人在用怎么还冲突」的纠结——冲突的那方往往就是 0.0.0.0 的隐形绑定。

和宿主机 EADDRINUSE 的关系

同一件事的两个视角:你的应用进程监听端口被占报 EADDRINUSE;你的容器映射端口被占报 port is already allocated。排查思路相通,详见:端口被占用怎么办?EADDRINUSE 排查。而「连不上某端口」的 ECONNREFUSED 是相反方向的问题,见:ECONNREFUSED 排查。

30 秒诊断脚本

动手前先跑:docker ps(哪个容器现在占着口)、lsof -i :端口 或 ss -tlnp(哪个进程在听——包括 docker-proxy)、compose ps。这回答唯一重要的问题:口被忘掉的容器(别的项目的孤儿)、已停未删容器的 docker-proxy、还是宿主进程(你的 dev server)占着。各有各的解法;瞎猜浪费的是五分钟热缓存的重启。

停止的容器为什么还占口

docker stop 保留容器对象;docker run -p 发布的口属于这个对象,部分网络模式下已停容器直到删除都有 docker-proxy 活着。意外场景:项目 A compose down 而项目 B 在默认桥上共享重叠端口映射——compose 只管自己的。防复发习惯:每个项目 compose 用唯一宿主端口(8081、8082…)而不是全抢 8080;项目收尾跑 docker container prune。

IPv6:隐形占口者

lsof -i :8080 查不到但 Docker 说已分配,多半是口绑在 IPv6(:::8080)而你查了 IPv4。Docker 默认双栈发布;绑 0.0.0.0:8080 的宿主服务与 :::8080 不冲突直到有请求要双栈。ss -tlnp 两行都显示。修法是显式绑定(-p 127.0.0.1:8080:80)——不该公开可达的服务本来就该这么绑。

Compose 的 ports: 与 expose:(以及 host 网络)

expose: 只是向关联容器声明元数据,不会在宿主机上绑定端口,因此不会触发 port is already allocated。冲突来自 ports:(或 docker run -p)。另一极端是 host 网络(network_mode: host / --network host):进程直接绑宿主协议栈,Docker 的发布映射为空,失败形态回到容器日志里的经典 EADDRINUSE。同一 compose 里若有的服务用 ports:、有的用 host 网络,请用对应工具分别查(docker ps --filter publish= vs 宿主上的 ss)。

用 docker inspect 读冲突

过滤器太吵时,直接看占坑者:

docker inspect -f '{{.Name}} {{json .HostConfig.PortBindings}} {{json .NetworkSettings.Ports}}' <id>

PortBindings 是你声明的;NetworkSettings.Ports 是守护进程实际编好的。创建失败后两者不一致(例如创建中重启 daemon)时,常出现僵死的 docker-proxy——应删容器再重建,而不是只重启容器内进程。

改写 compose 前,可把长串 docker run -p ... 贴进 docker run 转 compose,确认生成的 ports: 宿主机侧互不重复。

排查清单

  1. docker ps --filter "publish=<端口>" — 有容器占着吗?
  2. ss -tlnp | grep :<端口> — 宿主机进程占着吗?(两查都要做)
  3. 旧容器:docker rm -f;compose 场景先 docker compose down
  4. 都没人占却仍报错?systemctl restart docker 清 docker-proxy 残留
  5. 复用端口注意绑定地址:127.0.0.1 与 0.0.0.0 冲突,与特定网卡 IP 不冲突
  6. 勿把 ports: 排查手法套到 network_mode: host 的服务上

排查注记

第一步的两条命令(docker ps --filter publish 和 ss -tlnp)要求都跑,是因为占用者分容器和宿主机进程两类、处理方式完全不同——这是全文的排查主干。开篇特意先纠正主语:被占的是 -p 冒号左边的宿主机端口,容器内部端口那层走 NAT 随便重复。反直觉细节「绑定地址不同可共存」单独成节,是因为冲突判定按「地址+端口」组合,正好解释「明明没人在用怎么还冲突」——冲突的那一方往往是 0.0.0.0 的隐形绑定。


感谢阅读这篇 ToolVault 工具匣 技术文章。相关工具:Dockerfile 生成器、docker run 转 compose、CIDR 子网计算、HTTP 状态码速查。相关阅读:端口被占用 EADDRINUSE 排查、ECONNREFUSED 排查。访问 首页 查看更多开发者工具。