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 以项目名(默认 = 目录名)管理容器。常见的两种撞车:
- 改了
docker-compose.yml的端口想「再起一套」,但旧一套docker compose up的容器还在——先docker compose down; - 两个不同目录恰好同名(比如都叫
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: 宿主机侧互不重复。
排查清单
docker ps --filter "publish=<端口>"— 有容器占着吗?ss -tlnp | grep :<端口>— 宿主机进程占着吗?(两查都要做)- 旧容器:
docker rm -f;compose 场景先docker compose down - 都没人占却仍报错?
systemctl restart docker清 docker-proxy 残留 - 复用端口注意绑定地址:127.0.0.1 与 0.0.0.0 冲突,与特定网卡 IP 不冲突
- 勿把
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 排查。访问 首页 查看更多开发者工具。
相关工具
相关文章
Linux crontab 定时任务实战:命令、日志与排查
crontab 不只是写表达式:三个必会命令、/etc/cron.d 与用户 crontab 的区别、任务没跑的五步排查法、PATH/时区/% 转义、MAILTO,以及秒级任务的正确解法。
docker run 转 docker-compose 完全指南:为什么转、怎么转、参数对照表
把 docker run 命令迁移到 docker-compose.yml 的完整指南:compose 相比裸 run 的五大优势、参数逐项对照表(端口/卷/环境变量/重启策略/网络)、常见坑(裸 -e 透传、匿名卷、--rm 语义),附在线转换器用法。
Permission denied (publickey) 怎么解决?Git 推送失败的 6 种原因与修复
git clone/push 报 Permission denied (publickey) fatal: Could not read from remote repository?覆盖公钥没生成、没加载进 agent、没添加到 GitHub/Gitee、多账号配错密钥、deploy key 权限、remote URL 写错六种根因,附 ssh -v 诊断方法。