Python 容器内连接 PostgreSQL 失败的常见原因与解决方案

1次阅读

Python 容器内连接 PostgreSQL 失败的常见原因与解决方案

本文详解 docker compose 环境下 python 应用(如使用 psycopg2)无法连接 postgresql 容器的根本原因:服务间通信应使用容器内网端口(5432),而非宿主机映射端口(6544),并提供可立即生效的修复方案。

本文详解 docker compose 环境下 python 应用(如使用 psycopg2)无法连接 postgresql 容器的根本原因:服务间通信应使用容器内网端口(5432),而非宿主机映射端口(6544),并提供可立即生效的修复方案。

在 Docker Compose 构建的多容器应用中,一个高频但易被忽视的网络误区是:混淆“宿主机端口映射”与“容器间通信端口”。你的 docker-compose.yml 中已正确定义了 PostgreSQL 服务:

postgres:   image: postgres:latest   environment:     POSTGRES_USER: admin     POSTGRES_PASSWORD: admin1234     POSTGRES_DB: db   ports:     - "6544:5432"  # ← 宿主机 6544 → 容器内部 5432   networks:     - my-network   hostname: postgres

此处 ports: [“6544:5432”] 的含义是:将宿主机的 6544 端口转发到 PostgreSQL 容器监听的默认端口 5432。该映射仅对宿主机外部(如本地 pgAdmin、浏览器、curl 或本机运行的 Python 脚本)有效;而同一 my-network 网络内的其他容器(如 python-service)必须直接访问 postgres:5432 —— 因为它们通过 Docker 内置 DNS 解析 postgres 主机名,并直连容器 IP 的 5432 端口,不经过宿主机端口转发层

因此,你原始代码中硬编码的连接配置:

con_db = psycopg2.connect(     dbname="db",     user="admin",     password="admin1234",     host="localhost",  # ❌ 错误:在 python-service 容器内,localhost 指向自身,非 PostgreSQL     port="6544"        # ❌ 错误:6544 在容器内未监听,PostgreSQL 只监听 5432 )

存在两个关键问题:

立即学习Python免费学习笔记(深入)”;

  • host=”localhost”:在容器内,localhost 解析为 127.0.0.1(即 python-service 自身),而非 postgres 服务;
  • port=”6544″:6544 是宿主机端口,PostgreSQL 容器内部实际监听的是 5432,该端口在容器网络中才可达。

✅ 正确做法是:

  1. 使用服务名作为 host(Docker DNS 自动解析);
  2. 使用容器内实际端口 5432
  3. 通过环境变量注入配置,提升可维护性与安全性。

✅ 修复步骤

第一步:更新 docker-compose.yml 中 python-service 的环境变量

python-service:   build:     context: ./python-translate-api     dockerfile: Dockerfile   ports:     - "5000:5000"   depends_on:     postgres:       condition: service_completed_successfully   environment:     DB_HOST: postgres      # ✅ 服务名,Docker 自动解析     DB_PORT: "5432"        # ✅ 容器内真实端口     DB_NAME: db     DB_USER: admin     DB_PASSWORD: admin1234   networks:     - my-network

第二步:重构 Python 连接逻辑,读取环境变量

import os import psycopg2 from psycopg2 import sql  # 从环境变量安全读取配置(带默认值兜底,便于本地调试) DB_HOST = os.getenv("DB_HOST", "localhost") DB_PORT = os.getenv("DB_PORT", "5432") DB_NAME = os.getenv("DB_NAME", "db") DB_USER = os.getenv("DB_USER", "admin") DB_PASSWORD = os.getenv("DB_PASSWORD", "admin1234")  try:     con_db = psycopg2.connect(         host=DB_HOST,         port=DB_PORT,         dbname=DB_NAME,         user=DB_USER,         password=DB_PASSWORD     )     print(f"✅ 成功连接 PostgreSQL ({DB_HOST}:{DB_PORT})") except psycopg2.OperationalError as e:     print(f"❌ 数据库连接失败: {e}")     raise

? 提示:depends_on 仅确保容器启动顺序,不保证 PostgreSQL 服务已就绪。生产环境建议添加连接重试逻辑(例如使用 tenacity 库)或健康检查。

⚠️ 注意事项与最佳实践

  • 禁止在容器内使用 localhost 访问同网络其他服务:localhost 永远指向当前容器,跨服务通信必须使用 service-name(如 postgres);
  • 端口映射 ≠ 容器内端口:ports 字段仅影响宿主机访问,容器间通信永远使用 EXPOSE 或镜像默认端口(PostgreSQL 为 5432);
  • 环境变量优先于硬编码:避免敏感信息泄露,也便于多环境切换;
  • 验证网络连通性(调试时):进入 python-service 容器执行 ping postgres 和 nc -zv postgres 5432,确认 DNS 与端口可达;
  • Java 能连而 Python 不能? 很可能 Java 服务也配置了 host=postgres + port=5432,或其 Dockerfile/启动脚本已正确适配容器网络。

✅ 总结

根本症结在于网络作用域理解偏差:6544 是给宿主机用的“入口”,而 5432 才是容器网络中的“真实地址”。修正 host 为服务名、port 为容器内端口,并配合环境变量管理,即可彻底解决 psycopg2 连接拒绝(Connection refused)问题。这一原则同样适用于 redisrabbitmq 等所有 Docker 化中间件的客户端连接配置。

text=ZqhQzanResources