docker 容器端口连接被拒:connection refused 速查
更新时间:2026-10-24。FAQ 速查页,面向把服务塞进容器、端口也映射了、curl 却回
connection refused的同学。这个报错的语义很精确:TCP 包到达了目标 IP 和端口,但那里没有任何进程在听——内核直接回了 RST。所以排查的本质是一层层回答「包到底走到哪、谁该听而没在听」。病灶收敛在六根轴上:服务绑死 127.0.0.1 没绑 0.0.0.0、-p映射写反或没写、容器根本没起来或进程崩了、宿主机防火墙拦截、容器间用 localhost 或错名字通信、主机端口被别的进程占用。本篇只讲「连接被拒」(端口没人听);镜像拉不下来看 docker 镜像找不到速查,命令期权限问题看 docker 权限被拒速查。
本文要回答的问题
- 容器里服务明明起来了,宿主机
curl localhost:8080为什么 refused? -p 8080:80两个数字哪个是宿主机的、哪个是容器的?- 为什么容器里必须把服务绑到
0.0.0.0而不是127.0.0.1? - 容器之间互相访问,用
localhost为什么必炸? docker ps里端口映射显示正常,但外面还是连不上,下一步查什么?
一、报错原文与典型现象
connection refused 在不同客户端措辞略异,但都是 TCP RST 这一件事(工程路径以 /proj/demo 占位):
$ curl http://localhost:8080/api
curl: (7) Failed to connect to localhost port 8080 after 5 ms: Connection refused$ docker exec app1 curl http://localhost:3000
curl: (7) Failed to connect to localhost port 3000: Connection refused
# app1 想连 app2,却在自己容器里找 3000$ docker run -p 8080:80 myapp
docker: Error response from daemon: driver failed programming external connectivity:
Bind for 0.0.0.0:8080 failed: port is already allocated# 应用日志里的同语义报错(以 Node/Python 客户端为例):
Error: connect ECONNREFUSED <容器IP>:5432
ConnectionRefusedError: [Errno 111] Connection refused读报错的关键是分清方向:宿主机连容器 refused、容器连另一个容器 refused、还是起容器时端口绑定失败,三条路的修法完全不同。注意区分 refused 与超时——refused 是包到了没人听,超时是包根本没到(防火墙 DROP、网络不通),本篇管前者。
二、病因处置速查表
| 报错现象 / 场景 | 可能原因 | 快速验证 | 处置 |
|---|---|---|---|
| 宿主机 curl refused,容器内自连正常 | 服务绑了 127.0.0.1 没绑 0.0.0.0 | docker exec 容器 ss -tlnp 看监听地址 | 改应用监听地址为 0.0.0.0 |
| 宿主机 refused,容器内也连不通 | 容器内进程没起来或已崩 | docker ps 状态、docker logs 容器 | 修应用启动,再起容器 |
| 起容器时报 port is already allocated | 宿主机端口被别的进程/旧容器占 | sudo ss -tlnp | grep :8080 | 换宿主机端口或停掉占用者 |
| 映射存在但 refused | -p 参数写反(写成了 容器口:宿主机口 的预期错位) | docker port 容器 看实际映射 | 按 宿主机口:容器口 重写 |
| 容器间互访 refused | 用了 localhost(指自己)或不在同一网络 | docker network inspect 看成员 | 用容器名/服务名互访,加入同一自定义网络 |
| 同网络用名字仍 refused | 目标服务没监听或端口不对 | 进源容器 curl 目标名:端口 分步验 | 确认目标服务监听与端口 |
| 外部机器 refused,本机正常 | 防火墙/安全组没放行宿主机端口 | sudo iptables -L 或云平台安全组 | 放行端口;云主机改安全组 |
| compose 起的服务 refused | ports 段缩进/格式错导致没映射 | docker compose ps 看 PORTS 列 | 修正 compose 的 ports 写法 |
三、排查决策图
下面这张图是标准排查路线,颜色按角色区分:红是报错入口与命中的根因,蓝是排查动作,绿是通过分支,橙是修复动作,紫是防复发沉淀。
读这张图抓住排查顺序:先确认容器活着(docker ps)、映射在(PORTS 列),再进容器看服务绑在哪(ss -tlnp)。三层各有一个杀手级病灶:容器层是进程没起、映射层是 -p 写反或漏写、应用层是绑了 127.0.0.1。容器间互访 refused 的,先自问是不是用了 localhost。
四、逐条排查展开
头号杀手:服务绑在 127.0.0.1。 这是 docker 新手报错率最高的一条。很多框架开发模式默认监听 127.0.0.1——这在容器里意味着「只听本容器内部的回环」,宿主机端口映射把包转发进来后,容器的 eth0 接口上没人听,直接 RST。确认:
docker exec myapp ss -tlnp
# 看到 127.0.0.1:8080 就是中招;0.0.0.0:8080 或 :::8080 才对修在应用侧:Flask 用 app.run(host="0.0.0.0"),Node/Express 用 listen(port, "0.0.0.0"),nginx 确认 listen 不带 127.0.0.1 前缀。镜像交付的标准是容器内监听 0.0.0.0。
-p 的方向:宿主机口在前。 docker run -p 8080:80 是「宿主机 8080 → 容器 80」。想要的效果是访问 localhost:8080 打到容器 80。验证实际映射:
docker port myapp
# 80/tcp -> 0.0.0.0:8080 左边是容器口,右边是宿主机口只看到容器口没映射(跑的时候忘了 -p)的话,容器还活着也可以不重建:用 compose 管理的改文件重 up;裸 run 的只能带上 -p 重起——docker 不支持给运行中容器补映射。
容器没起或进程秒崩。 docker ps 里没有,先 docker ps -a 看是不是 exited,再 docker logs 容器名 看崩因。健康检查没过的容器,docker 不会给它接流量(swarm/compose 依赖条件场景)。这一步过不了,后面全是白查。
容器间通信:localhost 是头号误区。 每个容器有自己的网络命名空间,容器 A 里的 localhost:3000 指的是 A 自己,永远到不了容器 B。正确姿势是把它们放进同一个自定义网络,用容器名或 compose 服务名当主机名互访:
docker network create demo
docker run -d --name db --network demo mydb
docker run -d --name app --network demo -p 8080:8080 myapp
# app 内连 db:5432,不是 localhost:5432 也不是 172.x 手写 IP手写容器内网 IP 能通但脆弱——容器重建 IP 就变;自定义网络的内嵌 DNS 才是正路。默认 bridge 网络没有名字解析,这也是「--link 时代教程」与现代写法的分水岭。
宿主机端口被占。 起容器时报 port is already allocated,说明宿主机这个端口已经有主——可能是上一个没停干净的旧容器、或本机原生跑的服务。sudo ss -tlnp | grep :8080 找到占用者,停掉或给新容器换个宿主机口(-p 8081:80)。
防火墙与安全组是「本机通、外网不通」的分水岭。 本机 curl 通、别的机器 refused 或超时:Linux 看 firewalld/iptables/nftables 有没有放行;云主机看安全组入方向——docker 的 FORWARD 链规则与云平台安全组是两层,都要开。区分 refused 与超时:refused 多半是到了机器没人听(映射没生效),超时多半是路上被 DROP(防火墙)。
最小闭环复验。 修完别只在一头验,做双向三段:
docker exec myapp curl -sS http://127.0.0.1:80/ # 容器内自证服务在听
curl -sS http://localhost:8080/ # 宿主机过映射
docker exec app curl -sS http://db:5432/ 2>&1 | head -1 # 容器间(按需)三段全通才算闭环;哪段断了,问题就锁定在对应那一层。
五、规范做法与预防
- 镜像标准里写死监听 0.0.0.0:Dockerfile README 注明服务监听地址要求,容器化改造第一步就是它。
- compose 固化 ports 与网络:ports 用引号写
"8080:80",服务间互访只写服务名;自定义 network 声明在文件里,不靠手敲。 - 健康检查进定义:
HEALTHCHECK或 compose 的healthcheck段让「进程起了但没法服务」显式可见。 - 启动探活进 CI:流水线里起容器后 curl 一次健康端点,映射错、绑 127.0.0.1 当天现形。
- 端口分配登记:团队共用机器/服务器上谁用哪个宿主机口有张表,避免 already allocated 互踩。
- 报错先分方向:把「宿主机连容器 / 容器连容器 / 起容器失败」写进团队排错手册开头,减少乱修。
这一篇在知识体系里的位置
本篇是 docker 排错速查簇的网络篇,定位「容器跑起来了但端口连不上」:镜像拉取阶段的 manifest unknown 与 pull access denied 看 docker 镜像找不到速查;起容器、挂卷阶段的 permission denied 看 docker 权限被拒速查。docker ps 看映射、ss -tlnp 看监听地址、自定义网络加服务名互访,这三件套是容器网络排错的通用底盘。
一句话总结
docker 容器端口 connection refused 按三段排查:先看 docker ps 与 logs 确认容器活着、PORTS 列映射在(-p 宿主机口:容器口 别写反、别漏写);再 docker exec 进容器 ss -tlnp——监听在 127.0.0.1 就改应用绑 0.0.0.0,这是新手头号杀手;容器间互访禁用 localhost,进同一自定义网络用服务名解析。宿主机口被占换端口,本机通外网不通查防火墙与云安全组;修完容器内、宿主机、容器间三段 curl 复验闭环,compose 固化 ports/network/healthcheck,这类报错即可基本绝迹。