Docker 网络完整梳理:从容器互通到宿主机访问,一篇搞懂 Docker 网络
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
并把 backend 和 database 放进去。
此时 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 网络的大部分问题都可以定位清楚。
更多推荐



所有评论(0)