从Docker容器内连接宿主机的localhost

从Docker容器内连接宿主机的localhost

技术背景

在使用Docker进行开发和部署时,经常会遇到需要从容器内部连接到宿主机上运行的服务的场景。例如,一个运行在Docker容器内的Nginx实例需要连接到宿主机上运行的MySQL数据库。由于容器有自己独立的网络命名空间,默认情况下,容器内的localhost指向容器自身,而不是宿主机。因此,需要一些特定的配置和方法来实现从容器内连接到宿主机的localhost

实现步骤

不同操作系统和Docker版本的通用方法

Docker v 20.10及以上(所有平台)

  • 开发环境中,可以使用特殊的DNS名称host.docker.internal,它会解析为宿主机使用的内部IP地址。
    • Linux系统:在docker run命令中添加--add-host=host.docker.internal:host-gateway参数,或者在docker-compose.yml文件的容器定义中添加以下内容:
1
2
extra_hosts:
- "host.docker.internal:host-gateway"
- **注意**:部分用户反馈,该特殊DNS名称仅在Docker的默认桥接网络中有效,在自定义网络中可能无法使用。

旧版本的macOS和Windows Docker

  • Docker v 18.03及以上:同样可以使用host.docker.internal。不过在Linux系统中,当时还不支持该特性。
  • 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地址别名,例如:
1
sudo ifconfig lo0 alias 123.123.123.123/24
2. 确保服务监听该IP地址或`0.0.0.0`。如果服务仅监听`localhost`(`127.0.0.1`),将无法接受连接。
3. 在容器中指向该IP地址,即可访问宿主机。可以在容器内运行`curl -X GET 123.123.123.123:3000`进行测试。
4. 该别名在每次重启后会重置,如有需要,可以创建一个启动脚本。

Docker网络模式相关方法

--network="bridge"(默认模式)

  1. Docker默认会创建一个名为docker0的桥接网络。可以在宿主机上使用sudo ip addr show docker0查看其IP地址:
1
2
3
4
5
6
7
[vagrant@docker:~] $ sudo ip addr show docker0
4: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
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
inet6 fe80::5484:7aff:fefe:9799/64 scope link
valid_lft forever preferred_lft forever
这里宿主机在`docker0`网络接口上的IP地址是`172.17.42.1`。
  1. 启动一个新容器并进入其shell:docker run --rm -it ubuntu:trusty bash,在容器内使用ip addr show eth0查看其主网络接口的配置:
1
2
3
4
5
6
7
root@e77f6a1b3740:/# ip addr show eth0
863: eth0: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 66:32:13:f0:f1:e3 brd ff:ff:ff:ff:ff:ff
inet 172.17.1.192/16 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::6432:13ff:fef0:f1e3/64 scope link
valid_lft forever preferred_lft forever
这里容器的IP地址是`172.17.1.192`。查看路由表:
1
2
3
4
5
root@e77f6a1b3740:/# route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
default 172.17.42.1 0.0.0.0 UG 0 0 0 eth0
172.17.0.0 * 255.255.0.0 U 0 0 0 eth0
可以看到宿主机的IP地址`172.17.42.1`被设置为默认路由,并且可以从容器内访问:
1
2
3
4
5
root@e77f6a1b3740:/# ping 172.17.42.1
PING 172.17.42.1 (172.17.42.1) 56(84) bytes of data.
64 bytes from 172.17.42.1: icmp_seq=1 ttl=64 time=0.070 ms
64 bytes from 172.17.42.1: icmp_seq=2 ttl=64 time=0.201 ms
64 bytes from 172.17.42.1: icmp_seq=3 ttl=64 time=0.116 ms
  1. 要从桥接模式的容器中访问宿主机上运行的MySQL,需要确保MySQL服务监听172.17.42.1这个IP地址。可以在MySQL配置文件(my.cnf)中设置bind-address = 172.17.42.1bind-address = 0.0.0.0
    • 如果需要设置一个包含网关IP地址的环境变量,可以在容器内运行以下代码:
1
export DOCKER_HOST_IP=$(route -n | awk '/UG[ \t]/{print $2}')
然后在应用程序中使用`DOCKER_HOST_IP`环境变量来打开与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`这个IP地址。

--network="host"

  1. host网络模式运行Docker容器,该容器将与宿主机共享网络栈。从容器的角度来看,localhost(或127.0.0.1)将指向宿主机。
    • 查看宿主机的IP配置:
1
2
3
4
5
6
7
[vagrant@docker:~] $ ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 08:00:27:98:dc:aa brd ff:ff:ff:ff:ff:ff
inet 10.0.2.15/24 brd 10.0.2.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::a00:27ff:fe98:dcaa/64 scope link
valid_lft forever preferred_lft forever
- 从以`host`模式运行的Docker容器中查看网络配置:
1
2
3
4
5
6
7
[vagrant@docker:~] $ docker run --rm -it --network=host ubuntu:trusty ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 08:00:27:98:dc:aa brd ff:ff:ff:ff:ff:ff
inet 10.0.2.15/24 brd 10.0.2.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::a00:27ff:fe98:dcaa/64 scope link
valid_lft forever preferred_lft forever
可以看到,宿主机和容器共享相同的网络接口,具有相同的IP地址。
  1. 要从以host模式运行的容器中访问宿主机上的MySQL,可以在MySQL配置中保留bind-address = 127.0.0.1,并从容器中连接到127.0.0.1
1
2
3
4
5
6
7
8
9
10
11
12
13
[vagrant@docker:~] $ docker run --rm -it --network=host mysql mysql -h 127.0.0.1 -uroot -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 36
Server version: 5.5.41-0ubuntu0.14.04.1 (Ubuntu)

Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>
**注意**:要使用`mysql -h 127.0.0.1`而不是`mysql -h localhost`,否则MySQL客户端将尝试使用Unix套接字进行连接。

其他方法

挂载MySQL套接字文件

可以将宿主机上的mysqld.sock文件挂载到容器内部。

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

mysqld.sock可能的位置包括:

  • /tmp/mysqld.sock
  • /var/run/mysqld/mysqld.sock
  • /var/lib/mysql/mysql.sock
  • /Applications/MAMP/tmp/mysql/mysql.sock(如果通过MAMP运行)

使用自定义脚本获取宿主机IP地址

可以使用脚本动态获取宿主机的IP地址,并在docker-compose.yml中使用:

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

docker-compose.yml示例:

1
2
3
4
5
6
myapp:
build: .
ports:
- "80:80"
extra_hosts:
- "dockerhost:$DOCKERHOST"

然后在代码中将http://localhost改为http://dockerhost

核心代码

docker-compose.yml中使用host.docker.internal示例

1
2
3
4
5
6
7
8
version: '3'
services:
myservice:
image: myimage
ports:
- "8080:8080"
extra_hosts:
- "host.docker.internal:host-gateway"

从容器内获取宿主机IP地址的脚本

1
2
3
#!/bin/bash
DOCKER_HOST=$(docker run --rm alpine ip route | awk 'NR==1 {print $3}')
docker-compose up

最佳实践

  • 开发环境:在开发环境中,推荐使用host.docker.internal,因为它简单方便,无需手动配置IP地址。
  • 生产环境:在生产环境中,不建议使用host.docker.internal,因为它可能在某些环境中不可用。可以考虑将所有依赖服务都容器化,并使用Docker网络进行通信。
  • 安全性:如果将MySQL的bind-address设置为0.0.0.0,需要确保设置了适当的防火墙规则,以防止外部网络的非法访问。

常见问题

  • host.docker.internal无法解析
    • 检查Docker版本是否支持host.docker.internal
    • 对于Linux系统,确保在docker run命令中添加了--add-host=host.docker.internal:host-gateway参数,或者在docker-compose.yml中添加了相应的extra_hosts配置。
    • 部分用户反馈,该特殊DNS名称仅在Docker的默认桥接网络中有效,检查容器是否在默认桥接网络中。
  • 使用--network="host"模式的风险:使用--network="host"模式会使容器与宿主机共享网络栈,容器中打开的任何端口都会在宿主机上打开,这可能会带来安全风险。同时,无法通过共享的Docker网络和DNS访问其他容器,需要使用发布的端口来访问其他容器化应用程序。
  • MySQL无法连接
    • 检查MySQL的bind-address配置是否正确。
    • 确保容器可以访问宿主机的IP地址和端口。可以使用pingtelnet命令进行测试。
    • 如果使用挂载mysqld.sock的方法,确保文件路径和权限正确。

从Docker容器内连接宿主机的localhost
https://119291.xyz/posts/2025-04-22.connect-to-localhost-from-docker-container/
作者
ww
发布于
2025年4月23日
许可协议