Docker 网络的核心问题可以概括成三类:

1. 容器如何访问外部网络?
2. 容器之间如何互相访问?
3. 容器如何访问宿主机上的服务?

理解这三类问题之后,Docker 部署 Web、数据库、MQTT、Redis、后台服务等应用时,网络问题基本都能排查清楚。


一、Docker 网络的本质

Docker 容器默认不是直接运行在宿主机网络里,而是运行在 Docker 创建的虚拟网络环境中。

可以简单理解为:

宿主机 Linux
    ↓
Docker 虚拟网桥
    ↓
容器 A
容器 B
容器 C

每个容器通常会有自己的:

IP 地址
网卡
路由表
DNS 配置
端口空间

所以容器里的:

127.0.0.1

指的是容器自己,不是宿主机。

这是 Docker 网络里最容易混淆的一点。


二、查看 Docker 网络

在宿主机上执行:

docker network ls

通常会看到:

NETWORK ID     NAME      DRIVER    SCOPE
xxxxxx         bridge    bridge    local
xxxxxx         host      host      local
xxxxxx         none      null      local

常见网络类型有:

bridge:默认桥接网络,最常用
host:容器直接使用宿主机网络
none:容器没有网络
overlay:跨主机容器网络,常用于集群
macvlan:让容器像局域网内独立主机一样存在

普通单机部署里,最常用的是:

bridge
自定义 bridge 网络
host

三、默认 bridge 网络

如果你直接运行:

docker run -d --name app nginx

不指定网络时,容器默认会接入 Docker 的默认 bridge 网络。

可以查看:

docker inspect app

或者:

docker network inspect bridge

你会看到容器被分配了一个 Docker 内部 IP,例如:

172.17.0.2

宿主机上的 Docker 网桥通常是:

docker0

可以查看:

ip addr show docker0

常见结果类似:

docker0: 172.17.0.1/16

这表示:

宿主机 docker0 网桥地址:172.17.0.1
容器地址:172.17.0.x

四、默认 bridge 的特点

默认 bridge 网络有几个特点:

1. 容器可以访问外部网络
2. 容器可以通过 IP 互相访问
3. 默认情况下,容器之间不能稳定地通过容器名互相访问
4. 外部访问容器服务,需要做端口映射

例如启动一个 Nginx:

docker run -d --name web nginx

这个容器内部监听 80 端口。

但是宿主机浏览器不能直接访问:

http://宿主机IP:80

因为容器端口没有发布出来。

需要这样启动:

docker run -d --name web -p 8080:80 nginx

这表示:

宿主机 8080 端口 → 容器 80 端口

浏览器访问:

http://宿主机IP:8080

才会进入容器的 80 端口。


五、端口映射 ports 的含义

Docker 端口映射格式:

-p 宿主机端口:容器端口

例如:

docker run -d -p 8080:80 nginx

含义是:

访问宿主机 8080
    ↓
转发到容器 80

如果写:

docker run -d -p 3307:3306 mysql

含义是:

访问宿主机 3307
    ↓
转发到容器 3306

注意:

容器内部程序监听的是容器端口
外部用户访问的是宿主机端口

六、端口映射的几种写法

1. 指定宿主机端口

-p 8080:80

表示:

宿主机 8080 → 容器 80

2. 指定宿主机 IP

-p 127.0.0.1:8080:80

表示:

只允许宿主机本机访问 127.0.0.1:8080
外部机器不能访问

这种方式适合数据库、Redis 等不想暴露给外部的服务。


3. 随机宿主机端口

-p 80

Docker 会随机分配一个宿主机端口。

查看映射:

docker port 容器名

七、容器访问外部网络

默认情况下,容器可以访问外部网络。

例如:

docker run --rm busybox ping -c 3 8.8.8.8

如果可以 ping 通,说明容器可以访问外网。

容器访问外部网络时,通常通过宿主机做 NAT 转发。

逻辑是:

容器
  ↓
docker0 网桥
  ↓
宿主机网络
  ↓
外部网络

所以容器通常可以主动访问外部服务,例如:

访问公网 API
访问外部数据库
访问外部 MQTT Broker
访问外部 HTTP 服务

但外部不能直接访问容器内部服务,除非做端口映射。


八、容器之间如何互通?

容器之间互通,推荐使用自定义 bridge 网络

不要长期依赖默认 bridge 网络。

创建网络:

docker network create app-net

启动容器 A:

docker run -d --name service-a --network app-net nginx

启动容器 B:

docker run -it --name service-b --network app-net busybox sh

在容器 B 里面可以访问:

ping service-a

这时 service-a 会被 Docker 内部 DNS 解析到对应容器 IP。


九、自定义 bridge 网络的优势

自定义 bridge 网络比默认 bridge 更适合项目部署。

主要优势:

1. 容器之间可以通过容器名访问
2. 网络隔离更清晰
3. 不同项目可以使用不同网络
4. 容器 IP 变化后,服务名仍然可用
5. 可以动态把容器加入或移出网络

例如:

app-net
 ├── backend
 ├── mysql
 └── redis

后端服务可以访问:

mysql:3306
redis:6379

而不是写死 IP:

172.18.0.2:3306
172.18.0.3:6379

因为容器重启后 IP 可能变化,但容器名或服务名可以保持不变。


十、Docker Compose 网络

Docker Compose 会自动为一个项目创建默认网络。

例如 docker-compose.yml

services:
  backend:
    image: my-backend
    ports:
      - "8080:8080"

  database:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root

启动:

docker compose up -d

Compose 会自动创建一个网络,例如:

project_default

并把 backenddatabase 放进去。

此时 backend 容器里可以直接访问:

database:3306

因为 database 是 Compose 服务名。


十一、Compose 中自定义网络

更清晰的写法是手动声明网络:

services:
  backend:
    image: my-backend
    ports:
      - "8080:8080"
    networks:
      - app-net

  database:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root
    networks:
      - app-net

networks:
  app-net:
    driver: bridge

这样服务之间的关系很清楚:

backend 和 database 都在 app-net 网络中
backend 可以通过 database:3306 访问 database

十二、容器名、服务名、主机名的区别

在 Docker Compose 中,最常用的是服务名访问

例如:

services:
  api:
    image: my-api

  db:
    image: mysql:8.0

api 容器访问数据库时,应该写:

db:3306

这里的 db 是服务名。

不建议使用容器 IP,因为 IP 可能变化。

也不建议随便依赖 container_name,除非你明确需要固定容器名。

推荐方式:

服务之间通过 Compose service name 访问

十三、容器访问宿主机服务

这是 Docker 网络里最重要的问题之一。

首先要记住:

容器里的 127.0.0.1 是容器自己,不是宿主机

如果宿主机上有一个服务,例如:

宿主机服务:127.0.0.1:9000

容器里访问:

127.0.0.1:9000

访问的是容器自己,不是宿主机。


方案 1:使用宿主机 docker 网关地址

默认 bridge 网络中,宿主机 docker 网关通常是:

172.17.0.1

容器可以访问:

172.17.0.1:服务端口

查看网关:

docker network inspect bridge

找到:

Gateway

也可以查看:

ip addr show docker0

如果看到:

inet 172.17.0.1/16

那么容器可以尝试访问:

172.17.0.1:端口

方案 2:使用 host.docker.internal

在一些环境中,可以通过:

host.docker.internal

访问宿主机。

在 Linux 上,如果默认不可用,可以在启动容器时增加:

--add-host=host.docker.internal:host-gateway

Compose 写法:

extra_hosts:
  - "host.docker.internal:host-gateway"

这样容器内可以访问:

host.docker.internal:端口

它会被解析到宿主机网关地址。


方案 3:host 网络模式

如果使用 host 网络:

docker run --network host my-app

容器会直接使用宿主机网络。

这时容器里的:

127.0.0.1

就是宿主机的 127.0.0.1

但是 host 模式会降低网络隔离性,并且端口会直接占用宿主机端口。

一般不建议普通 Web 应用、数据库组合随便使用 host 模式。


十四、宿主机服务必须监听正确地址

即使容器能访问宿主机 IP,宿主机服务本身也必须允许外部连接。

例如宿主机上某服务只监听:

127.0.0.1:3306

这表示它只接受宿主机本机回环访问。

Docker bridge 容器从 172.17.0.x 访问时,可能会失败。

需要让服务监听:

0.0.0.0:3306

或监听宿主机的内网地址。

检查监听:

ss -lntp

例如:

ss -lntp | grep 3306

如果看到:

127.0.0.1:3306

说明只监听本地。

如果看到:

0.0.0.0:3306

说明所有网卡都监听。


十五、host 网络模式

host 网络模式的特点是:

容器不再拥有独立网络命名空间
容器直接使用宿主机网络
容器端口就是宿主机端口
-p 端口映射不再有意义

运行:

docker run --network host my-app

适合:

需要极低网络开销的服务
需要直接监听宿主机网络的服务
某些网络探测、监控、代理工具

不适合:

多个服务端口容易冲突的项目
需要网络隔离的项目
普通 Web + 数据库部署

host 模式下,如果容器内程序监听 8080,那么宿主机的 8080 也会被占用。


十六、none 网络模式

none 模式表示容器没有网络。

docker run --network none busybox

容器里通常只有本地回环接口。

适合:

不需要联网的离线计算任务
安全隔离任务
只处理本地文件的任务

普通服务部署很少使用。


十七、macvlan 网络

macvlan 可以让容器像局域网中的一台独立机器。

特点:

容器拥有局域网 IP
容器和宿主机同级出现在网络中
局域网其他设备可以直接访问容器 IP

示意:

局域网
 ├── 宿主机:192.168.1.10
 ├── 容器A:192.168.1.20
 └── 容器B:192.168.1.21

创建示例:

docker network create -d macvlan \
  --subnet=192.168.1.0/24 \
  --gateway=192.168.1.1 \
  -o parent=eth0 \
  macvlan-net

适合:

需要容器拥有独立局域网 IP
某些工业协议、广播发现、局域网直连场景

注意:

macvlan 配置比 bridge 复杂
宿主机和 macvlan 容器默认可能不能直接互通
需要额外配置路由或 macvlan 子接口

普通业务系统不建议一开始就使用 macvlan。


十八、overlay 网络

overlay 网络用于跨主机容器通信。

典型场景:

Docker Swarm
多台服务器组成集群
服务分布在不同宿主机上
容器之间仍然可以通过 overlay 网络互通

单机 Docker Compose 一般不需要 overlay。

如果只是单台 Ubuntu 服务器部署服务,使用:

user-defined bridge

就足够了。


十九、容器 DNS

Docker 自定义网络中,容器可以通过名称解析。

例如:

docker network create app-net

docker run -d --name redis --network app-net redis
docker run -it --name app --network app-net busybox sh

在 app 容器里:

ping redis

Docker 内部 DNS 会把 redis 解析成 Redis 容器 IP。

Compose 中也是一样。

例如:

services:
  api:
    image: api

  redis:
    image: redis

api 访问:

redis:6379

就是通过 Docker 内部 DNS 解析。


二十、容器 IP 为什么不建议写死?

容器 IP 不是稳定配置。

容器删除重建后,IP 可能变化。

例如原来:

db = 172.18.0.2

重启后可能变成:

db = 172.18.0.4

如果应用配置里写死 IP,就会连接失败。

推荐写:

db:3306
redis:6379
emqx:1883

也就是使用服务名或容器名。


二十一、网络隔离设计

一个比较好的 Docker 项目,不应该把所有容器都放在一个大网络里。

可以按功能划分网络。

例如:

services:
  gateway:
    image: nginx
    ports:
      - "80:80"
    networks:
      - public-net
      - backend-net

  api:
    image: my-api
    networks:
      - backend-net
      - db-net

  database:
    image: mysql
    networks:
      - db-net

networks:
  public-net:
  backend-net:
  db-net:

含义:

gateway 可以访问 api
api 可以访问 database
gateway 不能直接访问 database

这样隔离更清楚,也更安全。


二十二、ports 和 expose 的区别

ports

ports:
  - "8080:80"

表示把容器端口发布到宿主机。

外部可以访问:

宿主机IP:8080

expose

expose:
  - "3306"

只是声明容器内部服务端口,供同一 Docker 网络内部访问,不会发布到宿主机。

实际上,在同一个自定义网络里,即使不写 expose,容器之间通常也可以访问对方监听的端口。

所以实际项目中更常见的是:

Web 服务用 ports 暴露给外部
数据库、缓存、消息队列不暴露 ports,只放在内部网络

二十三、常见网络排查命令

查看 Docker 网络

docker network ls

查看某个网络详情

docker network inspect 网络名

例如:

docker network inspect bridge
docker network inspect app-net

查看容器网络配置

docker inspect 容器名

重点看:

Networks
IPAddress
Gateway
Ports

进入容器

docker exec -it 容器名 sh

如果容器有 bash:

docker exec -it 容器名 bash

测试 DNS

getent hosts 服务名

例如:

getent hosts redis

测试端口

容器里如果有 nc

nc -vz 服务名 端口

例如:

nc -vz database 3306
nc -vz redis 6379

如果容器里没有工具,可以临时启动 netshoot:

docker run --rm -it --network app-net nicolaka/netshoot

然后测试:

dig database
nc -vz database 3306
curl http://api:8080

查看宿主机监听端口

ss -lntp

查看指定端口:

ss -lntp | grep 8080

查看端口映射

docker port 容器名

例如:

docker port web

二十四、常见问题总结

问题 1:容器里访问 127.0.0.1 为什么不通?

因为容器里的 127.0.0.1 是容器自己。

如果目标服务在另一个容器中,应该用:

服务名:端口

如果目标服务在宿主机上,应该用:

宿主机网关IP:端口

或:

host.docker.internal:端口

问题 2:两个容器为什么不能用名字互相访问?

可能原因:

1. 不在同一个 Docker 网络
2. 使用了默认 bridge 网络
3. 容器名写错
4. 服务还没启动完成
5. 应用监听的是 127.0.0.1,不是 0.0.0.0

解决:

docker network create app-net
docker network connect app-net 容器名

或者使用 Compose,把服务放到同一个 networks 里。


问题 3:宿主机能访问容器,外部机器不能访问?

检查:

1. ports 是否正确映射
2. 服务是否监听 0.0.0.0
3. 宿主机防火墙是否放行端口
4. 云服务器安全组是否放行端口
5. 端口是否被其他服务占用

问题 4:容器访问宿主机服务失败?

检查:

1. 容器访问的不是 127.0.0.1
2. 宿主机服务是否监听 0.0.0.0 或 docker0 地址
3. 防火墙是否允许 Docker 网段访问
4. 用户权限或应用白名单是否允许远程连接
5. 端口是否真的在宿主机监听

问题 5:数据库容器要不要映射端口?

如果数据库只给其他容器访问,一般不需要:

ports:
  - "3306:3306"

只需要放到同一个 Docker 网络里,通过:

database:3306

访问即可。

只有当宿主机或外部工具需要直接连接数据库时,才考虑端口映射。

更安全的写法是:

ports:
  - "127.0.0.1:3306:3306"

这样只有宿主机本机可以访问。


二十五、推荐实践

Docker 网络设计建议:

1. 单项目使用自定义 bridge 网络
2. Compose 中服务之间用 service name 访问
3. 不要在配置里写死容器 IP
4. Web 服务才映射 ports 到宿主机
5. 数据库、Redis、MQTT 等内部服务尽量不暴露到公网
6. 容器访问宿主机服务时,不要用 127.0.0.1
7. 多层应用可以拆分 public-net、backend-net、db-net
8. 生产环境注意防火墙和安全组
9. 排查网络时优先用 docker inspect、docker network inspect、nc、curl

二十六、一张图总结

外部用户
   ↓
宿主机IP:8080
   ↓ ports 映射
Web 容器:80
   ↓ service name
API 容器:8080
   ↓ service name
数据库容器:3306

如果访问宿主机服务:

容器
   ↓
host.docker.internal 或 docker0 网关 IP
   ↓
宿主机服务端口

如果容器之间互通:

容器 A
   ↓
同一个 user-defined bridge 网络
   ↓
容器 B 服务名:端口

二十七、最终结论

Docker 网络可以总结成四句话:

1. 容器里的 127.0.0.1 永远优先理解为容器自己。
2. 容器之间通信,优先使用同一个自定义 bridge 网络和服务名。
3. 外部访问容器,需要使用 ports 端口映射。
4. 容器访问宿主机服务,需要使用宿主机网关地址或 host.docker.internal。

掌握这四点,Docker 网络的大部分问题都可以定位清楚。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐