# Docker 网络数据库连接指南 ## 问题描述 在后端服务连接运行在 Docker 网络中的数据库时,**不能使用 `localhost`** 作为连接地址。 ## 原因分析 ### 为什么使用 `localhost` 是错误的? 在 Docker 网络中,每个容器都有**独立的网络栈**: - **`localhost` 在容器内部指向容器自身**(127.0.0.1 指向容器内部) - **不是指向同一网络中的其他容器** 当你的后端容器尝试连接 `localhost:3306` 时,它实际上是在寻找**自己的 3306 端口**,而不是数据库容器的端口。 #### 直观理解 ``` ┌─────────────────────────────────────────────────┐ │ Docker 网络(我的网络) │ │ │ │ ┌──────────────────┐ ┌──────────────────┐ │ │ │ Backend 容器 │ │ Database 容器 │ │ │ │ │ │ │ │ │ │ localhost ───────┼──→ │ 自己的 8000 端口 │ │ │ │ (127.0.0.1) │ │ │ │ │ │ │ │ │ │ │ │ database ────────┼──→ │ 正确!指向 DB │ │ │ │ (服务名) │ │ │ │ │ └──────────────────┘ └──────────────────┘ │ │ ↑ ↑ │ │ └──────────┬───────────────┘ │ │ │ │ │ 只能通过服务名通信 │ └─────────────────────────────────────────────────┘ ``` #### 实际会发生什么 ```bash # 在 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` 中定义的服务名即可用作主机名: ```yaml services: backend: build: ./backend ports: - "8000:8000" depends_on: - database database: image: mysql:8.0 ports: - "3306:3306" environment: MYSQL_ROOT_PASSWORD: rootpassword ``` **连接配置:** ```python # 后端连接配置 DATABASE_URL = "mysql://user:password@database:3306/dbname" # ^^^^^^^^ 使用服务名 "database" ``` ### 2. 使用容器名 在 docker-compose 中为容器指定名称: ```yaml database: image: mysql:8.0 container_name: my-mysql-db # 显式指定容器名 ``` **连接配置:** ```python DATABASE_URL = "mysql://user:password@my-mysql-db:3306/dbname" ``` ### 3. 网络别名(Network Alias) 如果有多个服务需要连接,可以设置网络别名: ```yaml database: image: mysql:8.0 networks: appnet: aliases: - db - mysql networks: appnet: driver: bridge ``` **连接配置:** ```python # 可以使用任一别名 DATABASE_URL = "mysql://user:password@db:3306/dbname" # 或 DATABASE_URL = "mysql://user:password@mysql:3306/dbname" ``` ## 常见错误 ### ❌ 错误写法 ```python # 这会导致连接失败 DATABASE_URL = "mysql://user:password@localhost:3306/dbname" # ^^^^^^^^^ 指向 backend 自己 ``` ### ✅ 正确写法 ```python # 使用 docker-compose 中的服务名 DATABASE_URL = "mysql://user:password@database:3306/dbname" # ^^^^^^^^ 数据库服务名 ``` ## 实际示例 ### 示例 1:Spring Boot + MySQL ```yaml # 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 ```yaml # 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" ``` ```javascript // .env DATABASE_URL=postgres://user:password@postgres:5432/myapp // ^^^^^^^^ 服务名 ``` ### 示例 3:Python/Django + MongoDB ```yaml # 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" ``` ```python # settings.py MONGO_URI = "mongodb://mongodb:27017/mydb" # ^^^^^^^^ 使用服务名 ``` ## 调试技巧 ### 1. 进入容器测试连接 ```bash # 进入后端容器 docker exec -it bash # 测试数据库连接 ping database telnet database 3306 nc -zv database 3306 ``` ### 2. 查看网络信息 ```bash # 查看容器网络 docker network inspect # 查看容器 IP 和别名 docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ``` ### 3. 使用 docker network 连接 ```bash # 从宿主机通过容器 IP 连接(不推荐) docker inspect | grep IPAddress # 正确方式:从另一个容器连接 docker run --rm --network mysql:8.0 \ mysql -h database -u user -ppassword dbname ``` ## 特殊情况 ### 从宿主机连接数据库 如果需要从**宿主机**(非 Docker 容器)连接数据库,使用: ```yaml database: image: mysql:8.0 ports: - "3306:3306" # 映射到宿主机 ``` **连接方式:** ```python # 从宿主机运行的应用 DATABASE_URL = "mysql://user:password@localhost:3306/dbname" ``` **注意:** 这是唯一可以使用 `localhost` 的情况——从宿主机连接暴露端口的容器。 ### 开发环境 vs 生产环境 ```python # 开发环境:可能直接使用本地数据库 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`) - [ ] 检查防火墙规则 - [ ] 查看容器日志确认数据库是否正常启动 ## 参考资源 - [Docker Networking](https://docs.docker.com/network/) - [Docker Compose Service Discovery](https://docs.docker.com/compose/networking/) - [Connect to MySQL from another container](https://hub.docker.com/_/mysql) --- **最后更新:** 2026-04-21