Docker 容器里 localhost 为什么连不上 MySQL/Redis?一文讲透 Compose 网络、端口与排错
Docker 容器里 localhost 为什么连不上 MySQL/Redis?一文讲透 Compose 网络、端口与排错
本文定位:可复现教程 + 故障排查手册
你将一次讲清:容器中的
localhost、HOST_PORT:CONTAINER_PORT、Compose DNS、service_healthy、host.docker.internal与六步排错路径。
阅读路线: 先理解地址模型 → 再运行示例 → 最后按错误类型排查。
明明数据库已经启动,宿主机也能连接,应用放进 Docker 后却报
Connection refused;把地址从localhost改成容器 IP,重启后又失效——这类问题的根源通常不是数据库,而是没有分清“当前机器是谁”。
无论连接 MySQL、PostgreSQL、Redis、RabbitMQ 还是另一个后端服务,只要你真正理解下面三件事,大部分 Docker 网络问题都能快速定位:
- 容器中的
localhost指向谁; 15432:5432两个端口分别给谁使用;- Compose 为什么应该使用服务名,而不是固定容器 IP。
本文给出一套可以复用的判断模型、完整 Compose 示例和六步排错命令。
一、最重要的结论:每个容器都有自己的 localhost
先看最常见的错误配置:
services:
api:
environment:
DATABASE_URL: postgresql://demo:demo@localhost:5432/demo
db:
image: postgres:18-alpine
当 api 进程运行在容器中时,localhost 或 127.0.0.1 指向的是 api 容器自己,不是宿主机,也不是 db 容器。

可以把宿主机和每个容器想成独立的房间。每个房间都有一部号码相同的“内部电话”——127.0.0.1,但这部电话只能拨回当前房间。
因此:
| 代码运行位置 | localhost 指向 |
|---|---|
| Windows、macOS 或 Linux 宿主机 | 宿主机自己 |
api 容器 | api 容器自己 |
db 容器 | db 容器自己 |
如果 PostgreSQL 在 db 容器的 5432 端口监听,而 api 容器访问 localhost:5432,操作系统会在 api 容器内部寻找 5432 端口。那里没有 PostgreSQL,结果通常就是:
Connection refused
正确地址应当是:
postgresql://demo:demo@db:5432/demo
这里的 db 是 Compose 文件中的服务名。
二、15432:5432 到底哪一个端口给容器用?
Compose 中常见的端口配置:
services:
db:
image: postgres:18-alpine
ports:
- "15432:5432"
它的含义是:
HOST_PORT:CONTAINER_PORT
宿主机端口:容器端口
也就是说,宿主机的 15432 被转发到 db 容器的 5432。

两种访问场景不能混用:
场景 A:宿主机访问数据库
在宿主机运行数据库客户端:
psql -h localhost -p 15432 -U demo -d demo
路径是:
宿主机 localhost:15432
↓ 端口映射
db 容器 5432
场景 B:api 容器访问 db 容器
容器之间在同一个 Compose 网络内直接通信:
db:5432
它们不需要绕到宿主机的 15432,也不依赖 ports。
记忆规则:宿主机访问容器使用发布端口;容器访问容器使用服务名和容器端口。
甚至可以不发布数据库端口:
services:
api:
# api 与 db 在同一网络中,仍然可以访问 db:5432
db:
image: postgres:18-alpine
expose:
- "5432"
expose 主要承担声明和文档作用;同一网络内的容器通信并不依赖把端口发布到宿主机。只有宿主机或外部网络需要直接访问数据库时,才使用 ports。
三、Compose 服务名为什么比容器 IP 可靠?
执行 docker compose up 时,Compose 默认会:
- 创建一个名为
<项目名>_default的网络; - 将没有显式指定网络的服务加入该网络;
- 通过 Docker 内置 DNS 注册服务名;
- 让同一网络中的容器通过服务名互相发现。
例如:
services:
api:
build: .
db:
image: postgres:18-alpine
api 可以直接解析主机名 db。在用户自定义网络中,Docker 的内置 DNS 地址通常是 127.0.0.11。

图源:Docker Docs《Networking in Compose》,截图保留了官方示例中的
postgres://db:5432与宿主机postgres://localhost:8001对比。
容器被重新创建后,IP 可能从 172.20.0.3 变成 172.20.0.7,但服务名仍然是 db。因此不要这样配置:
postgresql://demo:demo@172.20.0.3:5432/demo
应当始终写:
postgresql://demo:demo@db:5432/demo
这一点也解释了为什么“先用 docker inspect 找 IP,再把 IP 写进配置”只是临时绕过问题,并不是正确修复。
四、一个可以复现的正确示例
目录结构:
docker-network-demo/
├── compose.yaml
├── Dockerfile
├── requirements.txt
└── app.py
1. compose.yaml
services:
api:
build: .
environment:
DATABASE_URL: postgresql://demo:demo@db:5432/demo
depends_on:
db:
condition: service_healthy
ports:
- "8000:8000"
networks:
- backend
db:
image: postgres:18-alpine
environment:
POSTGRES_USER: demo
POSTGRES_PASSWORD: demo
POSTGRES_DB: demo
healthcheck:
test: ["CMD-SHELL", "pg_isready -U demo -d demo"]
interval: 3s
timeout: 3s
retries: 10
start_period: 5s
ports:
- "15432:5432"
networks:
- backend
networks:
backend:
注意数据库连接串:
postgresql://demo:demo@db:5432/demo
db:Compose 服务名;5432:PostgreSQL 在容器内部监听的端口;- 没有使用
localhost; - 没有使用宿主机发布端口
15432。
2. Dockerfile
FROM python:3.13-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
CMD ["python", "app.py"]
3. requirements.txt
flask==3.1.2
psycopg[binary]==3.2.9
4. app.py
import os
import psycopg
from flask import Flask, jsonify
app = Flask(__name__)
database_url = os.environ["DATABASE_URL"]
@app.get("/")
def index():
with psycopg.connect(database_url) as connection:
with connection.cursor() as cursor:
cursor.execute(
"select current_database(), inet_server_addr(), inet_server_port()"
)
database, server_address, server_port = cursor.fetchone()
return jsonify(
{
"status": "ok",
"database": database,
"server_address": str(server_address),
"server_port": server_port,
}
)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000)
这里 Flask 必须监听 0.0.0.0。如果只监听 127.0.0.1,即使发布了 8000:8000,从容器外部也可能无法访问。
5. 启动与验证
docker compose up --build
浏览器访问:
http://localhost:8000
预期得到类似结果:
{
"database": "demo",
"server_address": "172.20.0.2",
"server_port": 5432,
"status": "ok"
}
由于本文运行环境没有安装 Docker,本示例完成了 Python 语法检查和 Compose 结构检查,但没有在本文环境中实际拉取镜像运行;以上 IP 仅为格式示例,真实地址由 Docker 动态分配。
五、depends_on 不等于“数据库已经可以连接”

图源:Docker Docs《Control startup and shutdown order in Compose》。官方明确区分了“容器正在运行”和“服务已经 ready”。
下面的短写法只表达启动顺序:
depends_on:
- db
它能让 Compose 先启动 db 容器,再启动 api,但“容器进程已启动”不代表“数据库已完成初始化并可以接受连接”。
更可靠的写法是为数据库定义健康检查,再使用 service_healthy:
db:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U demo -d demo"]
interval: 3s
timeout: 3s
retries: 10
start_period: 5s
api:
depends_on:
db:
condition: service_healthy
生产系统中,应用自身仍应实现连接超时、有限重试和断线重连,因为数据库可能在运行期间重启。
六、不要把三类连接错误混为一谈

1. Name or service not known
这通常表示 DNS 解析失败,重点检查:
- 服务名是否拼错;
- 两个服务是否真的共享网络;
- 是否在一个 Compose 项目中;
- 跨项目时是否加入同一个 external network。
验证:
docker compose exec api getent hosts db
2. Connection refused
这意味着目标地址通常已经可达,但目标端口没有进程接受连接。重点检查:
- 数据库是否已经启动完成;
- 连接的是容器端口还是宿主机端口;
- 程序是否监听在正确端口;
- 服务是否只绑定了
127.0.0.1; - 容器健康检查是否通过。
3. Connection timed out
超时更像是数据包没有得到响应,重点检查:
- 两个容器是否加入同一网络;
- 防火墙、安全组或网络策略;
- 路由是否正确;
- 目标地址是否指向了错误网段。
4. password authentication failed
这已经不是网络层错误。它说明客户端大概率已经到达数据库,应转向检查:
- 用户名、密码、数据库名;
- 环境变量是否被 Compose 正确展开;
- 旧数据卷中是否保留了以前的账号配置。
七、六条命令,从外到内定位问题
第一步:检查容器状态
docker compose ps
确认容器是否运行、健康检查是否通过、端口是否按预期发布。
第二步:检查最终生效配置
docker compose config
它可以发现环境变量展开、多个 Compose 文件合并、缩进等问题。不要只看编辑器里的原始 YAML,要看 Compose 最终解析后的配置。
第三步:检查网络成员
docker network ls
docker network inspect <项目名>_backend
在 Containers 中确认通信双方是否同时存在。
第四步:从调用方容器验证 DNS
docker compose exec api getent hosts db
如果 db 无法解析,先修服务名和网络,不要继续调整数据库密码。
第五步:验证 TCP 端口
docker compose exec api nc -vz db 5432
部分精简镜像没有 nc,可以临时使用同网络调试容器:
docker run --rm --network <项目名>_backend nicolaka/netshoot nc -vz db 5432
第六步:查看目标服务日志
docker compose logs --tail=100 db
重点寻找初始化失败、数据目录权限、端口占用、配置错误和认证失败。
八、需要访问宿主机服务时怎么办?
有些数据库并不在容器中,而是运行在宿主机。此时容器仍然不能用 localhost 访问宿主机。

图源:Docker Docs《Explore networking how-tos on Docker Desktop》,包含官方
host.docker.internal示例命令。
在 Docker Desktop 中,可以使用:
host.docker.internal
例如:
redis://host.docker.internal:6379
在 Linux Docker Engine 中,可显式增加 host-gateway 映射:
services:
api:
extra_hosts:
- "host.docker.internal:host-gateway"
然后应用仍然使用:
host.docker.internal
注意宿主机服务必须监听可被 Docker 网桥访问的地址;如果它只监听宿主机的 127.0.0.1,容器依然可能连接失败。
九、用网络分段减少不必要的横向访问
容器能够通信的前提是共享网络。利用这一点,可以让架构更符合最小权限原则。
services:
proxy:
image: nginx:alpine
ports:
- "80:80"
networks:
- frontend
api:
build: .
networks:
- frontend
- backend
db:
image: postgres:18-alpine
networks:
- backend
networks:
frontend:
backend:
通信关系如下:
这样:
- 外部只接触
proxy; proxy可以访问api;api同时连接前端和后端网络;db不加入前端网络;proxy无法直接访问数据库。
不要为了“省事”把所有服务都加入所有网络,也不要把数据库端口无条件发布到 0.0.0.0。
十、常见错误配置速查
| 错误写法 | 为什么错 | 推荐写法 |
|---|---|---|
容器内连接 localhost:5432 | 指向当前容器自己 | db:5432 |
容器内连接 db:15432 | 使用了宿主机发布端口 | db:5432 |
写死 172.20.0.3 | 重建后 IP 可能变化 | 使用服务名 db |
只有 depends_on: [db] | 不等待数据库真正就绪 | healthcheck + service_healthy |
服务只监听 127.0.0.1 | 其他容器无法访问 | 监听 0.0.0.0 或容器接口 |
| 两个服务不共享网络 | DNS 与路由均不成立 | 加入同一命名网络 |
所有端口都用 ports 发布 | 增大暴露面 | 只发布外部确实需要的端口 |
总结
Docker 网络排错不需要背很多命令,先建立正确的地址模型:
localhost永远指向当前网络命名空间自己;- 宿主机访问容器使用
HOST_PORT; - 容器之间使用
SERVICE:CONTAINER_PORT; - 服务名稳定,容器 IP 不稳定;
- 共享网络才具备直接通信条件;
- 先判断 DNS、拒绝、超时还是认证错误,再检查对应层。
下次再遇到 Connection refused,不要先重装 Docker,也不要急着把端口全部暴露。依次检查:
DNS → 网络成员 → 端口监听 → 应用就绪 → 认证配置
在哪一步失败,就优先修哪一层。
参考资料
- Docker Docs:Networking in Compose
- Docker Docs:Networking overview
- Docker Docs:Define and manage networks in Compose
- Docker Docs:Control startup and shutdown order
- Docker Docs:Docker Desktop networking how-tos
- Docker Docs:dockerd 的 host-gateway 配置
更多推荐
所有评论(0)