从 Docker 容器内部连接到主机的 localhost

从 Docker 容器内部连接到主机的 localhost

技术背景

在使用 Docker 时,经常会遇到需要从容器内部连接到主机的 localhost 的情况,例如访问主机上运行的 MySQL 服务等。但由于 Docker 容器有自己独立的网络命名空间,默认情况下,容器内的 localhost 指向容器本身,而不是主机的 localhost,因此需要特定的配置来实现容器与主机的连接。

实现步骤

不同 Docker 版本和平台的通用方法

Docker v 20.10 及以上

  • 所有平台(开发环境):使用内部 IP 地址,或连接到特殊 DNS 名称 host.docker.internal,它会解析为主机使用的内部 IP 地址。此方法仅适用于开发目的,在 Docker Desktop 之外的生产环境中不起作用。
  • Linux 平台
    • docker 命令中添加 --add-host=host.docker.internal:host-gateway 以启用该功能。
    • docker-compose.yml 文件的容器定义中添加以下内容:
1
2
extra_hosts:
- "host.docker.internal:host-gateway"

旧版本 macOS 和 Windows 的 Docker

  • Docker v 18.03 及以上:使用内部 IP 地址,或连接到特殊 DNS 名称 host.docker.internal,它会解析为主机使用的内部 IP 地址。Linux 支持待实现 [Support host.docker.internal DNS name to host #264][1]
  • Docker for Mac v 17.12 到 v 18.02:使用 docker.for.mac.host.internal 代替。
  • Docker for Mac v 17.06 到 v 17.11:使用 docker.for.mac.localhost 代替。
  • Docker for Mac 17.05 及以下
    1. 为网络接口附加一个 IP 地址别名,例如:sudo ifconfig lo0 alias 123.123.123.123/24
    2. 确保服务器监听上述 IP 地址或 0.0.0.0
    3. 在容器中指向该 IP 地址,例如:curl -X GET 123.123.123.123:3000

不同网络模式下的连接方法

docker run --network="bridge"(默认)

  1. Docker 默认创建一个名为 docker0 的桥接网络,主机和容器在该网络上都有 IP 地址。可以通过 sudo ip addr show docker0 查看主机在该网络上的 IP 地址。
  2. 启动一个新容器并进入容器,通过 ip addr show eth0 查看容器的 IP 地址,通过 route 查看路由表,可知主机的 IP 地址被设置为默认路由,容器可以访问该地址。
  3. 若要从容器访问主机上运行的 MySQL 服务,需要确保 MySQL 服务监听 172.17.42.10.0.0.0 地址。可以在 MySQL 配置文件(my.cnf)中设置 bind-address = 172.17.42.1bind-address = 0.0.0.0
  4. 在容器中运行以下代码设置环境变量:
1
export DOCKER_HOST_IP=$(route -n | awk '/UG[ \t]/{print $2}')
  1. 在应用程序中使用 DOCKER_HOST_IP 环境变量打开与 MySQL 的连接。

docker run --network="host"

  1. 运行 Docker 容器时将网络设置为 host,容器将与主机共享网络栈,从容器的角度看,localhost(或 127.0.0.1)将指向主机。
  2. 注意,容器中打开的任何端口都会在主机上打开,无需使用 docker run-p-P 选项。
  3. 若要从容器访问主机上运行的 MySQL 服务,可在 MySQL 配置中保持 bind-address = 127.0.0.1,并在容器中连接到 127.0.0.1,例如:
1
docker run --rm -it --network=host mysql mysql -h 127.0.0.1 -uroot -p

其他方法

挂载 mysqld.sock

  1. 在运行 MySQL 的主机上查找 mysql.sock 文件的位置:netstat -ln | awk '/mysql(.*)?\.sock/ { print $9 }'
  2. 将该文件挂载到 Docker 容器中预期的位置:docker run -v /hostpath/to/mysqld.sock:/containerpath/to/mysqld.sock

使用自定义脚本

编写脚本动态获取主机 IP 地址,并在 docker-compose.yml 中使用:

  1. 启动脚本(docker-run.sh
1
2
export DOCKERHOST=$(ifconfig | grep -E "([0-9]{1,3}\.){3}[0-9]{1,3}" | grep -v 127.0.0.1 | awk '{ print $2 }' | cut -f2 -d: | head -n1)
docker-compose -f docker-compose.yml up
  1. docker-compose.yml
1
2
3
4
5
6
myapp:
build: .
ports:
- "80:80"
extra_hosts:
- "dockerhost:$DOCKERHOST"
  1. 在代码中将 http://localhost 更改为 http://dockerhost

Linux 特定解决方案(内核 >=3.6)

  1. docker0 接口启用 route_localnet
1
sysctl -w net.ipv4.conf.docker0.route_localnet=1
  1. 添加 iptables 规则:
1
2
iptables -t nat -I PREROUTING -i docker0 -d 172.17.0.1 -p tcp --dport 3306 -j DNAT --to 127.0.0.1:3306
iptables -t filter -I INPUT -i docker0 -d 127.0.0.1 -p tcp --dport 3306 -j ACCEPT
  1. 创建 MySQL 用户,允许从任何地址(除 localhost)访问:
1
CREATE USER 'user'@'%' IDENTIFIED BY 'password';
  1. 在脚本中将 MySQL 服务器地址更改为 172.17.0.1

核心代码

获取主机 IP 地址的脚本

1
ifconfig | grep -E "([0-9]{1,3}\.){3}[0-9]{1,3}" | grep -v 127.0.0.1 | awk '{ print $2 }' | cut -f2 -d: | head -n1

docker-compose.yml 示例

1
2
3
4
5
6
7
8
version: '3'
services:
nginx:
build: ./
ports:
- "8080:80"
extra_hosts:
- "dockerhost:<yourIP>"

Linux 内核配置和 iptables 规则

1
2
3
sysctl -w net.ipv4.conf.docker0.route_localnet=1
iptables -t nat -I PREROUTING -i docker0 -d 172.17.0.1 -p tcp --dport 3306 -j DNAT --to 127.0.0.1:3306
iptables -t filter -I INPUT -i docker0 -d 127.0.0.1 -p tcp --dport 3306 -j ACCEPT

最佳实践

  • 在开发环境中,优先使用 host.docker.internal 方法,方便快捷。
  • 对于生产环境,建议将依赖服务也容器化,使用 Docker 网络进行通信,提高应用的可移植性和隔离性。
  • 使用 --network="host" 模式时要谨慎,因为它会降低容器的隔离性,可能存在安全风险。

常见问题

MySQL 连接问题

  • 问题:使用 bind-address = 0.0.0.0 时,MySQL 服务器会监听所有网络接口,可能导致从互联网访问,存在安全风险。
    • 解决方法:设置相应的防火墙规则,限制对 MySQL 服务的访问。
  • 问题:使用 bind-address = 172.17.42.1 时,MySQL 服务器不会监听 127.0.0.1,主机上的进程连接 MySQL 需使用 172.17.42.1 地址。
    • 解决方法:在主机上的应用程序中使用 172.17.42.1 地址连接 MySQL。

网络模式问题

  • 问题:使用 --network="host" 模式时,无法通过共享 Docker 网络的 DNS 访问其他容器,需要使用发布的端口访问。
    • 解决方法:确保其他容器的端口已正确发布,并使用发布的端口进行访问。

特殊 DNS 名称问题

  • 问题:特殊 DNS 名称 host.docker.internal 仅在 Docker 的默认 bridge 网络中有效,在自定义网络中可能无效。
    • 解决方法:使用其他方法,如手动配置 IP 地址或挂载 mysqld.sock

从 Docker 容器内部连接到主机的 localhost
https://119291.xyz/posts/2025-05-09.connect-to-localhost-from-docker-container/
作者
ww
发布于
2025年5月9日
许可协议