允许Docker容器连接到本地/主机的PostgreSQL数据库。

278
我最近一直在尝试使用Docker和QGIS,并按照this tutorial中的说明安装了一个容器。
一切都很顺利,但是我无法连接到包含所有地理信息系统数据的本地主机postgres数据库。我猜想这是因为我的postgres数据库没有配置为接受远程连接,所以我一直在编辑postgres配置文件,按照this article中的说明允许远程连接。
但是,当我尝试在Docker中运行的QGIS连接到我的数据库时,仍然出现错误信息:无法连接到服务器:Connection refused Is the server running on host "localhost" (::1) and accepting TCP/IP connections to port 5433?。Postgres服务器正在运行,并且我已经编辑了我的pg_hba.conf文件,允许从一系列IP地址(172.17.0.0/32)进行连接。我之前使用docker ps查询了docker容器的IP地址,尽管IP地址会变化,但迄今为止始终在172.17.0.x范围内。
有什么想法为什么我无法连接到这个数据库吗?我想可能是一些非常简单的问题!
我正在使用Ubuntu 14.04;Postgres 9.3。
15个回答

248

TL;DR

  1. 使用 IP 地址范围为 172.17.0.0/16,而非 172.17.0.0/32
  2. 不要使用 localhost 连接到你主机上的 PostgreSQL 数据库,而应使用主机的 IP 地址。为了保证容器的可移植性,使用 --add-host=database:<host-ip> 标记启动容器,并使用 database 作为连接 PostgreSQL 的主机名。
  3. 确保 PostgreSQL 配置为监听所有 IP 地址的连接,而非仅限于 localhost。在 PostgreSQL 的配置文件中查找设置项 listen_addresses,该文件通常位于 /etc/postgresql/9.3/main/postgresql.conf。(感谢 @DazmoNorton)。

Long version

172.17.0.0/32 不是 IP 地址的 范围,而是单个地址(即 172.17.0.0)。没有任何 Docker 容器会被分配到该地址,因为它是 Docker 桥接(docker0)接口的网络地址。

当 Docker 启动时,它将创建一个新的桥接网络接口,可以通过调用 ip a 轻松查看:

$ ip a
...
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN 
    link/ether 56:84:7a:fe:97:99 brd ff:ff:ff:ff:ff:ff
    inet 172.17.42.1/16 scope global docker0
       valid_lft forever preferred_lft forever

从我的情况来看,可以看到docker0接口具有IP地址172.17.42.1,子网掩码为/16(或255.255.0.0)。这意味着网络地址是172.17.0.0/16

IP地址是随机分配的,但如果没有其他配置,它将始终在172.17.0.0/16网络中。对于每个Docker容器,将从该范围中分配一个随机地址。

这意味着,如果您想允许所有可能的容器访问您的数据库,请使用172.17.0.0/16


1
嘿,感谢您的评论。我已将我的 pg_hba.conf 更改为您建议的地址,但在停止和重新启动 postgres 服务后仍然收到相同的连接错误消息。我已在我的 ipv4 连接下添加了该行 - 我是否应该在其他地方添加您建议的地址?或者,在运行于 Docker 中的 QGIS 应用程序中,我需要更改 postgres 连接信息吗?例如,如果我从 Docker 容器内部连接,主机仍然是“localhost”吗? - marty_c
啊,这是一个重要的点。不,localhost并不是你的Docker容器内部的主机系统。尝试连接主机系统的公共IP地址。为了保持容器的可移植性,你也可以使用--add-host=database:<host-ip>启动容器,并在Docker容器内使用database作为主机名连接到你的PostgreSQL主机。 - helmbert
9
我需要再加入一件东西。我还需要编辑 /etc/postgresql/9.3/main/postgresql.conf 并将我的服务器的 eth0 IP 地址添加到 listen_addresses 中。默认情况下,listen_addresses 仅绑定到本地主机上。 - Dzamo Norton
@DzamoNorton,感谢你的提示!我已经相应地更新了我的答案。 - helmbert
对于那些看到Docker在不同子网中生成容器(172.17172.20等)的人,请将以下内容放入您的 pg_hba.conf 文件中:host all all 172.0.0.0/8 md5 - GerardJP
显示剩余7条评论

184

16
目前为止,这是最佳答案!更加容易理解并且符合预期。 - bjm88
9
host.docker.internal 只限于 Mac 吗? - Dragas
19
仅支持 Windows 和 Mac 操作系统。对于我的情况,Ubuntu 无法使用。 - Azri Zakaria
这适用于WSL2 Ubuntu。我的具体情况是:WSL2 Docker容器(Flask应用程序)访问安装在WSL2的postgresql。 - WildGoose
3
文档中提到:“这仅用于开发目的,在 Docker Desktop 之外的生产环境中不起作用。” 我的生产环境中也将连接到远程数据库。在这种情况下,我该怎么办? - Mazhar Ali
显示剩余2条评论

78

适用于Mac的Docker解决方案

17.06版本及以上

感谢@Birchlabs的评论,现在可以使用这个仅限Mac的特殊DNS名称,使连接容器和主机服务变得非常简单:

docker run -e DB_PORT=5432 -e DB_HOST=docker.for.mac.host.internal

从17.12.0-cd-mac46版本开始,应该使用docker.for.mac.host.internal代替docker.for.mac.localhost。有关详细信息,请参见发行说明

旧版本

@helmbert的回答很好地解释了这个问题。但是Docker for Mac 没有暴露桥接网络,因此我不得不使用这个技巧来解决限制:

$ sudo ifconfig lo0 alias 10.200.10.1/24

打开/usr/local/var/postgres/pg_hba.conf文件,并添加以下行:

host    all             all             10.200.10.1/24            trust

打开 /usr/local/var/postgres/postgresql.conf 并编辑更改 listen_addresses

listen_addresses = '*'
重新加载服务并启动您的容器:
$ PGDATA=/usr/local/var/postgres pg_ctl reload
$ docker run -e DB_PORT=5432 -e DB_HOST=10.200.10.1 my_app 

这个解决方法基本上与@helmbert的答案相同,但是使用的是附加到lo0接口的IP地址,而不是docker0网络接口。


3
截至2017年4月4日,这个信息是否仍然有效? - Petrus Theron
我喜欢这种不会暴露数据库的方式。顺便问一下,我能在CentOS上使用吗?当我尝试使用你提供的别名命令时,出现了“alias: Unknown host”的错误。 - Tsung Wu
7
自Docker 17.06.0-rc1-ce-mac13(2017年6月1日)起,在macOS上有一种更好的方法。容器可以识别主机名docker.for.mac.localhost,这是您的主机机器的IP地址。您可以使用以下命令在容器的主机数据库中查找其条目:docker run alpine /bin/sh -c 'getent hosts docker.for.mac.localhost' - Birchlabs
7
看起来自18.03以来已更改为 host.docker.internal,其他选项仍可用但已弃用。(来源 - gseva

59

简单的解决方案

只需在docker run中添加 --network=host。就这么简单!

这样容器将使用主机的网络,因此localhost127.0.0.1将指向主机(默认情况下它们会指向一个容器)。例如:

docker run -d --network=host \
  -e "DB_DBNAME=your_db" \
  -e "DB_PORT=5432" \
  -e "DB_USER=your_db_user" \
  -e "DB_PASS=your_db_password" \
  -e "DB_HOST=127.0.0.1" \
  --name foobar foo/bar

11
小心!在我看来这是正确的解决方案,但它不适用于 macOS。不要浪费时间试图弄清为什么它不起作用。请查看:https://github.com/docker/for-mac/issues/2716 - jfcorugedo
2
适用于 Debian。尝试更改 postgresql.conf 和 pg_hba.conf,但这种方法更简单、更快速。 - frmbelz
1
此外,一个人无法发布端口。 - Curtis
这在我的 Ubuntu 20.04.4 LTS (GNU/Linux 5.13.0-1022-aws x86_64) 上运行良好,谢谢。 - Naser Nikzad

31

这里发布的解决方案对我无效。因此,我发布了这个答案来帮助遇到类似问题的人。

注意:该解决方案也适用于Windows 10,请查看下面的评论。

操作系统:Ubuntu 18
PostgreSQL:9.5(托管在Ubuntu上)
Docker:服务器应用程序(连接到PostgreSQL)

我正在使用docker-compose.yml构建应用程序。步骤1:请添加host.docker.internal:<docker0 IP>

version: '3'
services:
  bank-server:
    ...
    depends_on:
      ....
    restart: on-failure
    ports:
      - 9090:9090
    extra_hosts:
      - "host.docker.internal:172.17.0.1"

要查找docker的IP地址,例如172.17.0.1(在我的情况下),您可以使用以下命令:

docker inspect --format '{{ .NetworkSettings.IPAddress }}' container_id_or_name

$> ifconfig docker0
docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255

或者

$> ip a
1: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever

步骤2: 在postgresql.conf中,将listen_addresses更改为listen_addresses = '*'

步骤3: 在pg_hba.conf中添加以下行

host    all             all             0.0.0.0/0               md5

第四步: 使用以下命令重新启动PostgreSQL服务:sudo service postgresql restart

第五步: 请使用host.docker.internal主机名从服务器应用程序连接数据库。
例如:jdbc:postgresql://host.docker.internal:5432/bankDB

享受吧!!


2
对于那些想要编辑Postgres配置文件的人,可以使用命令sudo nano /etc/postgresql/<your_version>/main/postgresql.conf - Jasurbek Nabijonov
我已经在Windows 10上使用Docker验证了这些步骤。这个工作得很好。需要注意的是:在第1步中使用ipconfig,在第4步中,重新启动postgresql服务,转到控制面板->管理工具->查看本地服务,然后搜索Postgresql服务并重新启动它们。 - singhpradeep
1
谢谢,第二步和第三步解决了我的问题。在我的情况下,我将新的 pg_hba 条目的 method 设置为 password。此外,您不必打开对所有 IP(0.0.0.0/0)的访问权限,而是可以使用 Docker 桥接 IP(使用 ip -h -c a 查找该 IP),在我的情况下它是 172.17.0.1/16 - Leonard AB
如果您在Ubuntu上按照此方法操作时出现“无法将主机名“host.docker.internal”翻译为地址”的错误,则只需将主机更改为本地Docker IP“172.17.0.1”,应该就可以解决问题。 - Florent

11

在使用docker run命令时,您可以传递参数--network=host以访问容器内的localhost

例如:

docker run --network=host docker-image-name:latest

如果你想在localhost上传递环境变量,请使用--env-file参数访问容器内的环境变量。

例如:

docker run --network=host --env-file .env-file-name docker-image-name:latest

注意:在Docker镜像名称之前传递参数,否则参数将无法工作。 (我曾经遇到过这种情况,所以提醒大家!)

4
你要我如何在docker-compose中编写这个? - PirateApp
2
请注意,此选项会丢弃端口映射。在主机网络模式下,容器共享主机的网络命名空间。也就是说,如果您的容器中的应用程序运行在:80上,则在主机上也可以通过:80访问它。https://docs.docker.com/network/host/ - Konstantin K.
它只在Linux上工作。 - Konstantin K.

7

9
在主机上添加"networkd_mode"会断开另一个容器之间的连接。 - Bawantha

6

为了建立一个简单的东西,允许从docker容器到我的本地主机建立Postgresql连接,我在postgresql.conf文件中使用了以下内容:

listen_addresses = '*'

然后添加了这个pg_hba.conf文件:

host    all             all             172.17.0.0/16           password

然后进行重启。我的客户端(在容器中,IP地址为172.17.0.2)可以通过使用主机名、密码、数据库名称以及用户名和密码,在本地主机上运行的Postgresql进行连接。


172.17.0.0/16 是 Docker 的默认 IP 吗?如果不是,那么这对每个人来说都一样吗? - Awshaf Ishtiaque

5

三步解决方案

1. 更新docker-compose文件

首先,由于数据库是本地的,我们需要将主机网络绑定到容器上,通过向容器服务添加以下配置来实现

services:
    ...
    my-web-app:
        ...
        extra_hosts:
          -  "host.docker.internal:host-gateway"
        ...
    ...

2. 更新/etc/postgresql/12/main/pg_hba.conf文件

如果该目录下不存在该文件,请使用find / -name 'pg_hba.conf'命令查找。

在注释标签# IPv4 local connections:下添加以下行:

host    all             all             172.17.0.1/16           md5

3. 更新 /etc/postgresql/12/main/postgresql.conf

如果该文件在此目录下不存在,请使用 find / -name 'postgresql.conf' 命令查找它。

找到以下行(该行可能已被注释):

#listen_addresses = 'localhost'

将以下行更改为以下内容,以便能够从本地主机和172.17.0.1连接到Postgres

listen_addresses = 'localhost,172.17.0.1'

您还可以将其更改为以下内容,但不建议在生产环境中使用,因为这将使数据库暴露于全球(即任何IP地址都可以连接到数据库)。
listen_addresses = '*'

最后别忘了:

  1. 使用 sudo systemctl restart postgresql 重新启动 postgres 服务
  2. 将连接字符串的主机更新为 host.docker.internal

5

让我试着解释一下我所做的事情。

Postgresql

首先,我进行了必要的配置,以确保我的Postgres数据库可以接受来自外部的连接。

打开pg_hba.conf文件,在结尾添加以下行:

host    all             all             0.0.0.0/0               md5

打开 postgresql.conf 文件,查找 listen_addresses 并像这样进行修改:

listen_addresses = '*'

请确保上面的行没有被注释掉#

-> 重启你的数据库

注意:这不是生产环境下的推荐配置。

接下来,我查找了我的主机ip。我使用本地主机127.0.0.1,但容器看不到它,所以运行容器时会出现该连接被拒绝的消息。 在网上进行长时间搜索后,我发现容器可以看到您本地网络的内部ip(路由器分配给连接到它的每个设备的那个地址,我不是在说让您访问互联网的那个IP)。因此,我打开了一个终端并执行了以下操作:

查找本地网络ip

打开终端或CMD

(MacOS / Linux)

$ ifconfig

(Windows)

$ ipconfig

这个命令将显示您的网络配置信息。 并且长这样:

en4: 
    ether d0:37:45:da:1b:6e 
    inet6 fe80::188d:ddbe:9796:8411%en4 prefixlen 64 secured scopeid 0x7 
    inet 192.168.0.103 netmask 0xffffff00 broadcast 192.168.0.255
    nd6 options=201<PERFORMNUD,DAD>
    media: autoselect (100baseTX <full-duplex>)
    status: active

寻找处于活动状态的一个。

在我的情况下,我的本地网络IP是192.168.0.103

完成上述步骤后,运行容器。

Docker

使用--add-host参数运行容器,像这样:

$ docker run --add-host=aNameForYourDataBaseHost:yourLocalNetWorkIp --name containerName -di -p HostsportToBind:containerPort imageNameOrId

在我的情况下,我做了以下操作:

$ docker run --add-host=db:192.168.0.103 --name myCon -di -p 8000:8000 myImage

我正在使用 Django,所以 8000 端口是默认的。

Django 应用程序

访问数据库的配置如下:

settings.py 文件中。

DATABASES = {
    'default': {
            'ENGINE': 'django.db.backends.postgresql',
            'NAME': ‘myDataBaseName',
            'USER': ‘username',
            'PASSWORD': '123456',
            'HOST': '192.168.0.103',
            'PORT': 5432,
    }
}

参考资料

-p 标志说明: 使用网络端口映射进行连接

关于 docker run 命令: Docker run 文档

有趣的文章: Docker Tip #35: 连接到运行在 Docker 主机上的数据库


那个解决方案对我有用,它可以使用192.x.x.x,但无法使用localhost或127.x.x.x。 - Pratik Bhalodiya

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接