目录

Docker 网络教程:bridge、自定义网络与容器互连

当应用需要连接数据库、缓存等其他容器时,就需要了解 Docker 网络。最重要的一句话:将需要相互通信的容器放进同一个自定义网络,就可以使用容器名称互相连接。

Docker 自定义网络与端口映射示意图
浏览器通过 -p 映射的主机端口访问 app;app 在自定义网络中使用名称 db 连接数据库,数据库无需对外开放

网络驱动程序

刚安装好的 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),使用 IP 172.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。

主机无法连接容器

按顺序检查:

  1. 使用 docker ps 查看 PORTS 字段是否有 0.0.0.0:8080->80/tcp。
  2. 容器中的程序是否监听 0.0.0.0(上面的 Empty reply from server 就是典型症状)。
  3. 主机防火墙或云安全组是否允许该端口。

容器中无法连接互联网

通常是 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日