宿主机上 curl http://127.0.0.1:8000 正常,放进 Docker 的应用却报 Connection refused?先检查目标地址:普通 bridge 容器里的 localhost 指向容器自身,不是 VPS 宿主机。
如果数据库、API 也在另一个容器里,通常应该走服务名;如果服务确实直接运行在宿主机上,再考虑 host.docker.internal 与宿主机监听地址。本文主线是 Linux VPS 上常规 Docker Engine 的 bridge 网络,Docker Desktop、host 模式和 rootless 环境要按各自实现核对。
| 发起方与目标 | 应优先使用的地址 | 常见误写 |
|---|---|---|
| app 容器 → 同一网络的 backend 容器 | http://backend:8000 | localhost:8000 |
| app 容器 → VPS 上直接运行的 API | 宿主机可达地址或配置后的 host 名 | 容器自己的 127.0.0.1 |
| VPS 本机 → 已发布的容器端口 | 对应宿主机地址与发布端口 | 把容器端口与发布端口混用 |
| 用户浏览器 → VPS 上的服务 | 公开域名或授权入口 | Docker 内部服务名 |
还要确认 Docker 在哪里运行。远程 context 下,“宿主机”是 Docker 服务端所在机器,不是执行命令的笔记本。先看 docker context show,别在错误机器上修改监听或防火墙。
Docker Compose 网络文档说明,同一网络上的服务可以按服务名发现对方,不需要依赖会变化的容器 IP。连接时用的是对方容器内的监听端口。
下面是一个不发布公网端口的连通性演示。Python 镜像仅用于测试,不是正式应用上线模板:
services:
app:
image: python:3.14-alpine
command: ["python", "-c", "import time; time.sleep(3600)"]
environment:
BACKEND_URL: "http://backend:8000"
backend:
image: python:3.14-alpine
command: ["python", "-m", "http.server", "8000", "--bind", "0.0.0.0"]
两个服务默认加入本项目的默认网络。backend 在自己的容器里监听 8000;app 访问 backend:8000,不需要为了容器互联额外添加 ports。
在独立测试目录运行这份配置后,可从 app 内验证:
docker compose up -d
docker compose exec app python -c 'import os,urllib.request; print(urllib.request.urlopen(os.environ["BACKEND_URL"], timeout=5).status)'
示例预期返回 HTTP 状态码。不要覆盖生产项目,也不要把这个临时文件服务器当作正式 API。不同 Compose 项目默认网络不同,不能只因为两边服务名一样就假定互通;有跨项目需求时,应明确加入合适的共享网络,并避免重复网络别名。
当目标 API 确实运行在 VPS 本机,而不是 backend 容器中时,可在实际 app 服务中加入映射。下面是另一份独立演示配置:
services:
app:
image: python:3.14-alpine
command: ["python", "-c", "import time; time.sleep(3600)"]
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
BACKEND_URL: "http://host.docker.internal:8000"
这里没有启动宿主机 API,8000 端口必须由你实际的服务提供。extra_hosts 只建立名字到地址的映射,不会创建服务、修改它的监听地址,或自动放行防火墙。
Docker daemon 文档解释了特殊值 host-gateway:默认与宿主机默认网桥地址有关,也可以由 daemon 配置覆盖。因此不要把所有 VPS 都写死为 172.17.0.1,也不要把自定义网络的网关地址和这个特殊值无条件视为同一个地址。
Docker Engine 需要支持这个特殊值。Compose 能解析配置,不等于当前引擎就一定能创建成功;遇到不支持的错误,先核对引擎与平台,而不是把 host-gateway 当普通 DNS 域名去查询。
配置变更后,在原项目目录验证并更新 app:
docker compose config --quiet
docker compose up -d --no-deps app
服务名、-f、环境文件和项目名要与真实部署一致。已有数据先核对挂载与备份,不需要删除 Volume。只执行 restart 不一定应用新的容器配置,详见 Compose 修改后不生效排查。
先在宿主机上检查:
sudo ss -lntp '( sport = :8000 )'
ip -brief address
如果服务只监听 127.0.0.1:8000,经普通网桥访问宿主机其他地址,通常到不了这个回环监听。给容器增加一个 host 名,不会把宿主机回环端口自动转发到网桥。
应按应用能力绑定一个容器可达、且符合部署边界的宿主机地址,再只允许必要来源访问。如果服务只能监听回环,可以考虑受控的代理入口或调整部署方式。不要为了试通,就把数据库改成对所有接口监听并放开公网端口。
在前述 Python 演示容器中,可以分别查解析和 HTTP 连接:
docker compose exec app python -c 'import socket; print(socket.gethostbyname("host.docker.internal"))'
docker compose exec app python -c 'import os,urllib.request; print(urllib.request.urlopen(os.environ["BACKEND_URL"], timeout=5).status)'
真实镜像没有 Python 时,改用其已有的诊断工具;不要把“命令不存在”当作网络故障。这里验证的是 HTTP API,不能拿 curl 的 HTTP 成功标准去判断 MySQL、PostgreSQL 等非 HTTP 协议。
| 错误表现 | 说明方向 |
|---|---|
| 名字无法解析 | 映射没进入当前容器、名字拼错或环境不同 |
| Connection refused | 常见于目标地址没有监听,或连接被主动拒绝 |
| 连接超时 | 继续检查路由、过滤规则和服务是否能响应 |
| HTTP 401 / 数据库认证拒绝 | 已经收到服务端响应,转向认证与应用配置 |
宿主机防火墙、Docker 网络和服务监听要一起核对。不要把“公网端口能打开”当成“来自容器网桥也一定能访问”。
例如,若需要从 VPS 本机访问前述 backend,可以在 backend 下补充以下片段:
services:
backend:
ports:
- "127.0.0.1:18000:8000"
这表示将宿主机回环地址上的 18000 映射到容器的 8000,详见 Docker 端口发布文档。该片段需合并到包含 backend 镜像的配置中,不能作为完整应用单独启动。
同网络的 app 仍访问 backend:8000,不是 backend:18000。宿主机访问可使用对应发布地址;另一个普通 bridge 容器则不能把宿主机回环绑定的 18000,简单换成 host-gateway 地址就当作等价入口。
容器访问宿主机服务,本身也不要求给 app 添加 ports。方向弄反,是这一类故障最容易反复修改却没有效果的原因。
Docker Desktop 文档提供了特殊名字 host.docker.internal,并说明了其宿主机/虚拟机网络结构。不能把 Desktop 的默认行为直接套到 Linux VPS,也不应从 Mac 上找不到 docker0 就推断 VPS 的网络结构。
network_mode: host 则是另一种选择:host 驱动文档说明,它共享宿主机网络命名空间,不再为容器单独分配网络 IP。这会改变端口占用与网络隔离关系,不能当作普通 bridge 排障的通用补丁,也不要继续依赖原来的端口映射规则。
若问题来自 Nginx Proxy Manager 容器连不到宿主机程序,可将本文的地址、监听和网络检查应用到它的后端目标上,再看 Nginx Proxy Manager 配置指南。
先确认请求真正从目标容器发出、连接的是预期主机和端口,再验证一次实际业务请求。配置能解析、名字能解析、TCP 能连接和业务认证成功,是不同层次,不要只完成第一项就宣布修复。
本文已使用 Docker Compose 2.17.3 验证三组配置:共享网络示例、host-gateway 映射示例,以及端口发布片段与共享网络示例的合并结果。合并后发布地址为 127.0.0.1:18000、目标端口为 8000,app 的容器内地址仍为 backend:8000。当前本地 Docker 守护进程未运行,因此不把静态解析结果当成 Linux 网桥、host-gateway 或生产数据库连接成功的证明。
