应用搬进Docker后,经常会遇到一种反直觉现象:宿主机上用 curl 127.0.0.1:9000 明明正常,容器里访问同一个地址却失败。原因并不神秘,容器拥有独立的网络命名空间,容器里的localhost只代表容器自己,不会自动指向宿主机。
这类故障不能靠反复重启解决。更稳妥的做法是先记录具体错误,再沿着名称解析、容器路由、宿主机监听和防火墙逐层验证。连接拒绝、连接超时和域名解析失败指向的方向完全不同,第一步分错,后面很容易越改越乱。

先在容器内复现真实请求
先不要用宿主机上的测试结果代替容器内结果。进入发生故障的容器,使用应用相同的协议、端口和主机名测试:
docker exec -it CONTAINER sh
getent hosts host.docker.internal
curl -v --connect-timeout 3 http://host.docker.internal:9000/health
镜像没有curl时,可以根据镜像的包管理方式临时安装诊断工具,或者启动一个加入相同网络的临时诊断容器。不要为了测试直接改生产镜像,也不要把宿主机网络模式当作万能修复。
Could not resolve host 说明名称没有解析;Connection refused 通常表示路径已到目标地址,但对应端口没有接受连接;一直等待后超时,则更像防火墙丢包、路由错误或服务只在另一地址监听。保存完整输出和测试时间,后续才能与日志对齐。
应用通过HTTPS连接时,还要保留TLS握手和证书校验结果。把证书错误误判成网络不通,可能导致为了绕过校验而加入不安全参数;应先确认连接已经建立,再分别处理主机名与信任链。
理解容器里的localhost
容器中的 127.0.0.1 和 ::1 属于容器自己的回环接口。宿主机服务即使监听在宿主机的127.0.0.1,普通bridge网络中的容器也无法通过容器自己的127.0.0.1访问它。
先查看容器的地址和默认路由:
docker exec CONTAINER ip addr
docker exec CONTAINER ip route
docker inspect CONTAINER --format '{{json .NetworkSettings.Networks}}'
默认bridge环境中,容器默认网关通常对应宿主机上的Docker网桥地址,但具体地址不应硬编码。自定义网络、rootless Docker、Docker Desktop以及云主机上的额外网络策略,都可能改变实际路径。
在Linux上配置host-gateway
Docker Desktop通常提供 host.docker.internal。原生Linux Engine环境可以通过特殊值 host-gateway 映射一个稳定名称,运行单个容器时可写:
docker run --add-host host.docker.internal:host-gateway IMAGE
Compose可在对应服务中加入:
services:
app:
extra_hosts:
- "host.docker.internal:host-gateway"
修改后先执行 docker compose config 查看最终配置,再重新创建容器,并在容器内读取 /etc/hosts。只修改Compose文件却没有重建容器,旧容器不会自动得到新映射。
host-gateway 默认解析到Docker默认网桥相关的宿主机地址,守护进程也可以把它配置成其他地址。因此排查时应以容器内解析结果和宿主机实际接口为准,不能假设所有服务器都是固定的172.17.0.1。
多网卡服务器还要确认这个地址能否到达目标监听接口。宿主机管理网、业务网和Docker网桥并存时,一个可解析的地址未必就是服务允许访问的地址。用 ip route get 和实际请求源地址核对,比逐个猜测接口更快。
检查宿主机服务监听地址
拿到宿主机地址后,下一步是在宿主机确认服务究竟监听在哪里:
sudo ss -lntp | grep ':9000'
curl -v http://127.0.0.1:9000/health
curl -v http://HOST_BRIDGE_IP:9000/health
只看到 127.0.0.1:9000,说明服务只接受宿主机回环流量。容器通过网桥地址访问时,内核找不到对应监听套接字,通常会返回连接拒绝。需要让应用监听网桥地址或经过一个受控的反向代理,而不是盲目改成 0.0.0.0。
监听所有接口会扩大暴露面。调整前先确认云安全组、主机防火墙和应用认证,优先绑定明确的内部接口。还要同时检查IPv4与IPv6:服务只监听 ::1,而容器解析到IPv4地址,也会表现为端口不可用。
排查防火墙与转发规则
宿主机监听正常但容器仍然超时,应查看防火墙命中情况。不同发行版可能使用nftables、iptables前端或firewalld,先确认当前规则体系,避免同时修改多套规则。
sudo nft list ruleset
sudo iptables -S
sudo firewall-cmd --list-all
重点看来自Docker网桥网段、进入宿主机本地端口的INPUT规则。访问宿主机本地服务不等同于容器转发到公网,不能只检查FORWARD链。规则有计数器时,在容器发起一次请求前后对比计数,比凭感觉放行整个网段更可靠。
不要为了验证直接清空防火墙。可以添加仅允许目标网段、目标接口和目标端口的临时规则,确认有效后再持久化。云平台安全组通常约束进入云主机网卡的流量,不一定直接管理Docker网桥,但仍应核对实际拓扑。
请求到达宿主机却没有进入应用时,可在短时间内对目标接口和端口抓包。只采集报文头并限制数量,避免记录业务载荷;看到SYN到达却没有SYN-ACK,通常能把范围继续收敛到监听与本机过滤规则。
区分宿主机服务与其他容器服务
目标如果实际运行在另一个容器,就不应该绕到宿主机映射端口。把两个服务加入同一个用户自定义网络,然后使用Compose服务名和容器端口访问,路径更短,也不依赖宿主机端口是否发布。
docker network inspect NETWORK
docker compose exec app getent hosts database
docker compose exec app sh -c 'nc -vz database 5432'
服务间通信使用容器端口。例如数据库容器内部监听5432,宿主机发布15432,其他同网络容器仍应连接 database:5432。把15432写进容器间配置,既绕远又容易受到宿主机防火墙影响。
留意rootless与host网络差异
rootless Docker的网络栈与普通rootful Engine不同,默认网关、源地址和端口转发实现可能不一样。看到网上的172.17.0.1方案时,要先确认当前守护进程模式:
docker info | grep -i rootless
docker context show
docker version
--network host 会改变隔离方式,在Linux上容器可直接使用宿主机网络命名空间,但它会带来端口冲突和更大的网络可见范围,在Docker Desktop上的行为也不同。它适合经过评估的特殊场景,不适合仅为修复一个地址问题就仓促启用。
需要验证不同Engine版本或网络模式时,可以在 萤光云 建立隔离测试机,或在按小时计费的 LightNode 上复现最小Compose。只复制脱敏配置和空数据,不要上传生产密钥。
修改后怎样验收
验收不能只看一次curl成功。重新创建容器后,确认名称解析、目标地址、TCP连接和应用响应都符合预期,同时检查服务重启后配置仍然存在。
docker compose config
docker compose up -d --force-recreate app
docker compose exec app getent hosts host.docker.internal
docker compose exec app curl -fsS http://host.docker.internal:9000/health
再从宿主机检查端口没有意外暴露到公网,并观察应用日志中请求源地址是否符合访问控制。真正完成修复的标准,是容器重建后仍能稳定访问,目标服务只暴露在需要的接口和端口上。
常见问题
容器里访问127.0.0.1为什么不是宿主机?
容器有独立网络命名空间,自己的回环接口与宿主机分离。除非使用经过确认的host网络模式,否则localhost只指向当前容器。
可以直接使用172.17.0.1吗?
不建议硬编码。自定义网桥、rootless模式和守护进程配置都可能改变地址,使用host-gateway映射并在容器内验证更稳妥。
映射成功但还是Connection refused怎么办?
优先检查宿主机服务是否只监听127.0.0.1、端口是否正确以及IPv4和IPv6是否一致;映射只解决地址入口,不会改变服务监听范围。


