目录
Docker 网络教程:bridge、自定义网络与容器互连
当应用需要连接数据库、缓存等其他容器时,就需要了解 Docker 网络。最重要的一句话:将需要相互通信的容器放进同一个自定义网络,就可以使用容器名称互相连接。
网络驱动程序
刚安装好的 Docker 有三个内置网络:
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
a74caef4acb9 bridge bridge local
b9a49b39804b host host local
87a8a7fd2cd9 none null local
| 驱动程序 | 说明 |
|---|---|
bridge | 默认值。容器有自己的 IP,位于一个虚拟网段中 |
host | 容器直接使用主机的网络,不进行隔离(Linux 上效果最完整) |
none | 无网络 |
overlay | 跨多台主机(Docker Swarm) |
macvlan | 让容器像实体机器一样出现在局域网中 |
默认 bridge 的限制
未指定网络时,容器会连接到名为 bridge 的默认网络。在这个网络中,容器之间不能通过名称连接:
$ docker run -d --name web1 nginx:1.30
b1d3fb9024c112c5990d9ac85012cd3f5e38c114ab4409a06f960c6d8cdb496e
$ docker run --rm alpine:3.23 ping -c 1 -W 2 web1
ping: bad address 'web1'
$ docker inspect --format '{{.NetworkSettings.IPAddress}}' web1
template parsing error: template: :1:18: executing "" at <.NetworkSettings.IPAddress>: map has no entry for key "IPAddress"
$ docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' web1
172.18.0.2
$ docker run --rm alpine:3.23 ping -c 1 172.18.0.2
PING 172.18.0.2 (172.18.0.2): 56 data bytes
64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.058 ms
--- 172.18.0.2 ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.058/0.058/0.058 ms
$ docker rm -f web1
web1
- 使用名称
web1找不到容器(bad address),使用 IP172.18.0.2则可以连接。 - 但 IP 由 Docker 自动分配,重新创建容器后可能会变化,因此不能将它写死在配置中。
Docker 29 的变化:旧版本可以使用
{{.NetworkSettings.IPAddress}}获取容器 IP,Docker 29 已移除这个字段(上面的template parsing error)。请改用{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}。
因此,实际使用中几乎都会创建自定义网络。
自定义网络:通过名称互连
docker network create appnet
docker run -d --name db --network appnet -e POSTGRES_PASSWORD=secret postgres:18
$ docker network create appnet
fadfeb6028cb78335fa94cfd7536265256a914cc461caae1026b6ebb21e9d7a0
$ docker run -d --name db --network appnet -e POSTGRES_PASSWORD=secret postgres:18
227033e27591e6a1c195a6d63a141aaf4486c6285477792fe3080e1d4deeb2c0
$ docker run --rm --network appnet alpine:3.23 ping -c 2 db
PING db (172.19.0.2): 56 data bytes
64 bytes from 172.19.0.2: seq=0 ttl=64 time=0.069 ms
64 bytes from 172.19.0.2: seq=1 ttl=64 time=0.092 ms
--- db ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.069/0.080/0.092 ms
$ docker run --rm --network appnet -e PGPASSWORD=secret postgres:18 psql -h db -U postgres -c "SELECT version();"
version
--------------------------------------------------------------------------------------------------------------------
PostgreSQL 18.6 (Debian 18.6-1.pgdg13+2) on x86_64-pc-linux-gnu, compiled by gcc (Debian 14.2.0-19) 14.2.0, 64-bit
(1 row)
$ docker network inspect appnet --format "{{range .Containers}}{{.Name}} {{.IPv4Address}}{{println}}{{end}}"
db 172.19.0.2/16
- 在
appnet中,Docker 内置 DNS 会将名称db解析为数据库容器的 IP。 - 使用 PostgreSQL 客户端通过
-h db成功连接,并查询到数据库版本(PostgreSQL 18.6)。 - 数据库容器没有使用
-p,主机和外部都无法访问它,只有同一网络中的容器可以访问,安全性更高。
应用的连接配置也使用容器名称,例如:
docker run -d --name app --network appnet -p 3000:3000 \
-e DATABASE_URL=postgres://postgres:secret@db:5432/postgres \
myapp
注意:
- 容器之间连接时使用的是容器内的端口(5432),与
-p无关。 - 只有需要让主机或外部访问的服务(例如网站)才需要
-p。 - 容器名称应避免使用
web、app这类恰好也是顶级域名的词作为连接目标;在同一网络内没有问题,但从网络外查询时可能会解析到奇怪的地址,容易导致调试时误判。
一个容器加入多个网络、网络别名
$ docker network create appnet
77fadbc5633854c9e75fd9aa65325d0155ad33f5de453abac7f15f6766402bee
$ docker run -d --name app --network appnet nginx:1.30
25b4c1d92caeef5ca8765daf2f3ad6247982cacfad4914000ded8a0a455df851
$ docker network create frontend
45a9ab78fddb9f1b47a22b9ce156144286888c42eadc2b4f589986fe1f98cee0
$ docker network connect frontend app
$ docker inspect --format '{{range $k, $v := .NetworkSettings.Networks}}{{$k}} {{$v.IPAddress}}{{println}}{{end}}' app
appnet 172.19.0.2
frontend 172.20.0.2
$ docker run -d --name search --network appnet --network-alias es nginx:1.30
43b07713e7ec032d21e37fdf690064b389f798ed26b2715e7ea18c15840647af
$ docker run --rm --network appnet alpine:3.23 getent hosts es
172.19.0.3 es es
$ docker rm -f app search
app
search
$ docker network rm appnet frontend
appnet
frontend
docker network connect会将运行中的容器加入另一个网络,app现在有两个 IP。这常用于隔离:前端网络放置 Nginx 和应用,后端网络放置应用和数据库,这样 Nginx 就无法连接数据库。--network-alias es让同一网络中的其他容器也可以使用名称es连接到search。多个容器使用相同别名时,DNS 会返回多个 IP。
管理网络
docker network create appnet
docker network ls
docker network inspect appnet # 查看有哪些容器、IP
docker network connect appnet 容器名称 # 将运行中的容器加入网络
docker network disconnect appnet 容器名称
docker network rm appnet
docker network prune # 删除未使用的网络
端口映射:程序要监听 0.0.0.0
-p 8000:8000 会让 Docker 将发往主机 8000 端口的流量转发到容器网络接口上的 8000 端口。因此,容器中的程序必须监听 0.0.0.0(所有接口),不能只监听 127.0.0.1(容器自己的 localhost)。用 Python 内置网页服务器实际对比:
$ docker run -d --name localonly -p 8000:8000 python:3.14-slim python -m http.server 8000 --bind 127.0.0.1
246edb96a9affefaab188ac7d5ceec2fb103b511bdac352cca6ed3a80ae6764a
$ curl -sS http://localhost:8000
curl: (52) Empty reply from server
$ docker rm -f localonly
localonly
$ docker run -d --name allif -p 8000:8000 python:3.14-slim python -m http.server 8000 --bind 0.0.0.0
438fa44edf6aec790a7e6b2bfcdb7af33c16a14c661c50b8d9564ff52844e5e6
$ curl -s http://localhost:8000 | head -n 3
<!DOCTYPE HTML>
<html lang="en">
<head>
$ docker rm -f allif
allif
- 只监听
127.0.0.1时,虽然端口已经映射,curl却得到Empty reply from server:流量进入了容器,但程序只在容器自己的 localhost 上等待,无法收到流量。 - 改为监听
0.0.0.0后就正常了。
很多开发服务器默认只监听 localhost,在容器中运行时需要特别指定:
flask run --host 0.0.0.0
uvicorn main:app --host 0.0.0.0
vite --host 0.0.0.0
从容器连接主机
要让容器访问主机上的服务(例如主机上运行的数据库),请使用特殊名称 host.docker.internal:
$ docker run --rm alpine:3.23 getent hosts host.docker.internal
$ docker run --rm --add-host=host.docker.internal:host-gateway alpine:3.23 getent hosts host.docker.internal
172.18.0.1 host.docker.internal host.docker.internal
- Docker Desktop(Windows、macOS):内置支持
host.docker.internal。 - Linux:默认没有这个名称(第一条命令没有任何输出),需要添加
--add-host=host.docker.internal:host-gateway,这样它会被解析为主机在 Docker 网络中的地址。
主机上的服务也必须监听容器可以连接的接口,而不只是 127.0.0.1。
host 网络与 none 网络
docker run -d --network host nginx:1.30
容器直接使用主机的网络接口,nginx 监听 80 就是主机的 80,无需 -p(即使写了也会被忽略)。这种方式性能最好,但会失去隔离,也可能与主机上的程序争用端口。在 Docker Desktop 上的行为与 Linux 并不完全相同。
--network none 则完全没有网络,只有本地回环接口:
$ docker run --rm --network none alpine:3.23 ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
适用于不需要网络或不应连接网络的任务(例如处理不可信文件)。
疑难排解
容器之间使用 localhost 无法连接
每个容器都有自己的 localhost。在 app 容器中连接 localhost:5432,连接的是 app 自身,而不是数据库容器。请改用容器名称,例如 db:5432。
主机无法连接容器
按顺序检查:
- 使用
docker ps查看PORTS字段是否有0.0.0.0:8080->80/tcp。 - 容器中的程序是否监听
0.0.0.0(上面的Empty reply from server就是典型症状)。 - 主机防火墙或云安全组是否允许该端口。
容器中无法连接互联网
通常是 DNS 问题。可以在 /etc/docker/daemon.json(Docker Desktop 中为 Settings → Docker Engine)指定 DNS,修改后重启 Docker:
{ "dns": ["1.1.1.1", "8.8.8.8"] }
使用 Docker Compose 时,Compose 会自动为项目创建网络,服务之间直接使用服务名称即可连接。
重点回顾
- 默认 bridge 网络不提供名称解析,容器之间应使用自定义网络。
- 在自定义网络中使用容器名称或
--network-alias互连。 - 程序要监听
0.0.0.0,端口映射才会生效。 - 容器连接主机时使用
host.docker.internal。
动手练习
练习 1:创建网络 lab,启动一个名为 cache 的 redis:8-alpine 容器并加入该网络,再使用另一个容器运行 redis-cli -h cache ping 进行测试。
答案
docker network create lab
docker run -d --name cache --network lab redis:8-alpine
docker run --rm --network lab redis:8-alpine redis-cli -h cache ping
输出 PONG。网络命令参见 docker network。
常见问题
为什么在容器里使用 localhost 无法连接另一个容器?
每个容器都有自己的网络,localhost 指的是容器自身。要连接另一个容器,请将两者放在同一个自定义网络中,并使用容器名称连接。
容器如何连接主机上运行的服务?
Docker Desktop 使用 host.docker.internal;Linux 上添加 --add-host host.docker.internal:host-gateway,详见本章“从容器连接主机”。
Docker 29 如何获取容器的 IP?
{{.NetworkSettings.IPAddress}} 已被移除,请改用 docker inspect -f "{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}" 容器。
更新于 2026年10月14日