This repository has been archived on 2026-05-19. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files

9.3 KiB
Raw Permalink Blame History

Docker 网络数据库连接指南

问题描述

在后端服务连接运行在 Docker 网络中的数据库时,不能使用 localhost 作为连接地址。

原因分析

为什么使用 localhost 是错误的?

在 Docker 网络中,每个容器都有独立的网络栈:

  • localhost 在容器内部指向容器自身(127.0.0.1 指向容器内部)
  • 不是指向同一网络中的其他容器

当你的后端容器尝试连接 localhost:3306 时,它实际上是在寻找自己的 3306 端口,而不是数据库容器的端口。

直观理解

┌─────────────────────────────────────────────────┐
│              Docker 网络(我的网络)               │
│                                                  │
│   ┌──────────────────┐    ┌──────────────────┐  │
│   │   Backend 容器    │    │  Database 容器    │  │
│   │                  │    │                  │  │
│   │ localhost ───────┼──→ │  自己的 8000 端口 │  │
│   │ (127.0.0.1)     │    │                  │  │
│   │                  │    │                  │  │
│   │ database ────────┼──→ │ 正确!指向 DB    │  │
│   │ (服务名)         │    │                  │  │
│   └──────────────────┘    └──────────────────┘  │
│         ↑                          ↑            │
│         └──────────┬───────────────┘            │
│                    │                            │
│            只能通过服务名通信                    │
└─────────────────────────────────────────────────┘

实际会发生什么

# 在 backend 容器内执行
# 尝试连接 localhost:3306
# 结果:Connection refused
# 原因:backend 自己没有在 3306 端口监听服务

类比说明

这就好比:

  • 你和朋友住在同一个大楼(Docker 网络)
  • 你想找朋友
  • 你大喊"我在这里"(使用 localhost)
  • 但实际上你需要喊的是"小明"(使用服务的名字)
  • 因为 "我在这里" 指的是你自己,不是小明

Docker 的网络隔离机制

Docker 容器默认使用以下网络模式:

  1. Bridge 网络模式(默认):

    • 每个容器有独立的 IP 地址
    • 容器间通过服务名互相发现
    • localhost 仅指向容器自身
  2. 容器间的 DNS 解析:

    # 在 backend 容器内
    $ ping database
    PING database (172.19.0.2)  // 解析到数据库容器的 IP
    
    $ ping localhost
    PING localhost (127.0.0.1)   // 指向自己
    
  3. 端口监听位置:

    Database 容器内部:MySQL 监听 0.0.0.0:3306
    Database 容器 IP:172.19.0.2
    Backend 容器不能访问:127.0.0.1:3306(自己的端口)
    Backend 容器应该访问:172.19.0.2:3306(通过服务名解析)
    

结论

使用 localhost 连接数据库必然失败,因为它:

  • 永远指向调用方容器自己
  • 无法跨容器通信
  • 违反了 Docker 的网络隔离设计

正确做法:使用服务名或容器名,这样 Docker 的内置 DNS 才能正确解析到目标容器。

正确做法

1. 使用服务名(Service Name)

在 docker-compose.yml 中定义的服务名即可用作主机名:

services:
  backend:
    build: ./backend
    ports:
      - "8000:8000"
    depends_on:
      - database

  database:
    image: mysql:8.0
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: rootpassword

连接配置:

# 后端连接配置
DATABASE_URL = "mysql://user:password@database:3306/dbname"
#                          ^^^^^^^^ 使用服务名 "database"

2. 使用容器名

在 docker-compose 中为容器指定名称:

database:
  image: mysql:8.0
  container_name: my-mysql-db  # 显式指定容器名

连接配置:

DATABASE_URL = "mysql://user:password@my-mysql-db:3306/dbname"

3. 网络别名(Network Alias)

如果有多个服务需要连接,可以设置网络别名:

database:
  image: mysql:8.0
  networks:
    appnet:
      aliases:
        - db
        - mysql

networks:
  appnet:
    driver: bridge

连接配置:

# 可以使用任一别名
DATABASE_URL = "mysql://user:password@db:3306/dbname"
# 或
DATABASE_URL = "mysql://user:password@mysql:3306/dbname"

常见错误

❌ 错误写法

# 这会导致连接失败
DATABASE_URL = "mysql://user:password@localhost:3306/dbname"
#                              ^^^^^^^^^ 指向 backend 自己

✅ 正确写法

# 使用 docker-compose 中的服务名
DATABASE_URL = "mysql://user:password@database:3306/dbname"
#                              ^^^^^^^^ 数据库服务名

实际示例

示例 1:Spring Boot + MySQL

# docker-compose.yml
version: '3.8'
services:
  springboot-app:
    build: .
    ports:
      - "8080:8080"
    environment:
      SPRING_DATASOURCE_URL: jdbc:mysql://mysql-db:3306/mydb
      SPRING_DATASOURCE_USERNAME: user
      SPRING_DATASOURCE_PASSWORD: password
    depends_on:
      - mysql-db

  mysql-db:
    image: mysql:8.0
    environment:
      MYSQL_DATABASE: mydb
      MYSQL_USER: user
      MYSQL_PASSWORD: password
      MYSQL_ROOT_PASSWORD: rootpassword
    ports:
      - "3306:3306"

示例 2:Node.js + PostgreSQL

# docker-compose.yml
version: '3.8'
services:
  api:
    build: ./api
    ports:
      - "3000:3000"
    environment:
      DATABASE_HOST: postgres
      DATABASE_PORT: 5432
      DATABASE_NAME: myapp
    depends_on:
      - postgres

  postgres:
    image: postgres:14
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    ports:
      - "5432:5432"
// .env
DATABASE_URL=postgres://user:password@postgres:5432/myapp
//                                     ^^^^^^^^ 服务名

示例 3:Python/Django + MongoDB

# docker-compose.yml
version: '3.8'
services:
  web:
    build: .
    ports:
      - "8000:8000"
    environment:
      MONGO_HOST: mongodb
      MONGO_PORT: 27017
    depends_on:
      - mongodb

  mongodb:
    image: mongo:6.0
    ports:
      - "27017:27017"
# settings.py
MONGO_URI = "mongodb://mongodb:27017/mydb"
#            ^^^^^^^^ 使用服务名

调试技巧

1. 进入容器测试连接

# 进入后端容器
docker exec -it <backend-container> bash

# 测试数据库连接
ping database
telnet database 3306
nc -zv database 3306

2. 查看网络信息

# 查看容器网络
docker network inspect <network-name>

# 查看容器 IP 和别名
docker inspect <database-container> --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'

3. 使用 docker network 连接

# 从宿主机通过容器 IP 连接(不推荐)
docker inspect <database-container> | grep IPAddress

# 正确方式:从另一个容器连接
docker run --rm --network <network-name> mysql:8.0 \
  mysql -h database -u user -ppassword dbname

特殊情况

从宿主机连接数据库

如果需要从宿主机(非 Docker 容器)连接数据库,使用:

database:
  image: mysql:8.0
  ports:
    - "3306:3306"  # 映射到宿主机

连接方式:

# 从宿主机运行的应用
DATABASE_URL = "mysql://user:password@localhost:3306/dbname"

注意: 这是唯一可以使用 localhost 的情况——从宿主机连接暴露端口的容器。

开发环境 vs 生产环境

# 开发环境:可能直接使用本地数据库
if os.getenv("ENVIRONMENT") == "development":
    DATABASE_URL = "mysql://user:password@localhost:3306/devdb"
else:
    # 生产环境:使用 Docker 网络中的数据库
    DATABASE_URL = "mysql://user:password@database:3306/proddb"

最佳实践

  1. 使用环境变量:将数据库连接配置为环境变量,便于在不同环境间切换
  2. 使用服务名:优先使用 docker-compose 中的服务名,保持配置一致性
  3. 健康检查:使用 depends_on + 健康检查确保数据库就绪后再启动应用
  4. 网络隔离:为不同环境使用不同的网络
  5. 安全性:不要在代码中硬编码密码,使用 secrets 或环境变量

故障排查清单

  • 检查 docker-compose.yml 中数据库服务的名称
  • 确认后端使用的是服务名而非 localhost
  • 确认数据库服务在同一个网桥网络中
  • 检查端口映射配置(如果从宿主机连接)
  • 验证容器间的网络连通性(使用 ping 或 telnet)
  • 检查防火墙规则
  • 查看容器日志确认数据库是否正常启动

参考资源


最后更新: 2026-04-21