跳到主要内容

Docker 网络

容器默认拥有独立的网络空间。它可以主动访问外部网络,但外部不能仅凭容器内部端口访问它。本篇通过两个 Nginx 容器和一个临时客户端解释最常见的网络路径。

完成后,你将能够:

  • 区分容器端口与宿主机端口;
  • 使用 --publish 对外提供服务;
  • 创建自定义 bridge 网络;
  • 使用容器名进行 DNS 服务发现;
  • 排查端口占用、连接拒绝和名称解析问题。

1. 先理解三种访问路径

容器访问互联网

bridge 网络中的容器通常可以通过宿主机转发访问互联网,例如拉取软件包或请求外部 API。能否访问仍取决于宿主机 DNS、代理和防火墙策略。

容器访问容器

两个容器加入同一个自定义网络后,可以通过容器名通信,不必发布端口,也不要依赖会变化的容器 IP。

宿主机访问容器

需要显式发布端口,例如 -p 8080:80

  • 左边 8080:宿主机监听端口;
  • 右边 80:容器内应用监听端口;
  • 请求 localhost:8080 时,Docker 把流量转发到容器的 80 端口。

EXPOSE 80 只是镜像元数据,不会自动创建这条转发规则。

2. 查看默认网络

# 列出 Docker 已有网络;默认可见 bridge、host 和 none
docker network ls

# 查看默认 bridge 的子网、网关和已连接容器
docker network inspect bridge

常见网络驱动:

驱动典型用途新手需要知道的重点
bridge单台主机上的普通容器优先创建自定义 bridge 网络
hostLinux 上直接共享宿主网络无端口隔离;Docker Desktop 行为有限制
none完全禁用外部网络只保留回环接口
overlayDocker Swarm 的跨主机网络单机入门暂不需要

3. 端口映射实验

3.1 发布固定端口

# 将宿主机 8080 端口转发到容器 80 端口
docker run --detach \
--name network-web \
--publish 8080:80 \
nginx:1.27-alpine

# 查看 Docker 记录的实际端口映射
docker port network-web

# 从宿主机验证;响应状态应为 HTTP/1.1 200 OK
curl --head http://127.0.0.1:8080

docker psPORTS 列可能显示 0.0.0.0:8080->80/tcp[::]:8080->80/tcp,表示所有宿主机接口都可能接受连接。实际是否能从外部访问还取决于系统防火墙、云安全组和路由。

3.2 只允许本机访问

开发数据库、管理后台或由反向代理转发的服务,通常只绑定回环地址:

# 删除上一节容器,释放宿主机 8080 端口
docker rm --force network-web

# 显式绑定 127.0.0.1,局域网其他机器不能直接访问
docker run --detach \
--name network-web \
--publish 127.0.0.1:8080:80 \
nginx:1.27-alpine

# 输出应包含 127.0.0.1:8080
docker port network-web

3.3 让 Docker 随机选择端口

多个临时实例并行运行时,可以省略宿主机端口:

# 只写容器端口,Docker 会随机分配可用的宿主机端口
docker run --detach \
--name random-port-web \
--publish 80 \
nginx:1.27-alpine

# 查询随机分配的端口,例如 0.0.0.0:32768
docker port random-port-web 80

# 清理这个临时实例
docker rm --force random-port-web

端口默认使用 TCP。UDP 服务必须写明协议,例如 -p 5353:5353/udp

4. 创建容器专用网络

现在让客户端容器通过名称访问 Nginx,不经过宿主机端口。

# 创建用户自定义 bridge 网络
docker network create beginner-net

# 查看网络列表,确认 beginner-net 已创建
docker network ls --filter name=beginner-net

# 启动 Web 容器并加入该网络;不需要发布宿主机端口
docker run --detach \
--name internal-web \
--network beginner-net \
nginx:1.27-alpine

# 临时启动 curl 客户端并加入同一网络,通过容器名访问
docker run --rm \
--network beginner-net \
curlimages/curl:8.10.1 \
--head http://internal-web

最后一条命令应返回 HTTP/1.1 200 OK。这里的 internal-web 由 Docker 内置 DNS 解析,不需要知道容器 IP,也没有使用 -p

检查网络详情:

# 查看子网、网关和已连接容器;Containers 字段应包含 internal-web
docker network inspect beginner-net

# 提取 internal-web 在 beginner-net 中的 IP,仅用于观察
docker inspect internal-web \
--format '{{(index .NetworkSettings.Networks "beginner-net").IPAddress}}'
使用名称,不使用 IP

容器删除重建后 IP 可能改变。应用配置应使用容器名或 Compose 服务名,例如 http://internal-webpostgres://db:5432

5. 动态接入和断开网络

一个容器可以连接多个网络,以分隔不同通信范围:

# 创建第二个实验网络
docker network create extra-net

# 把已运行的 internal-web 接入第二个网络
docker network connect extra-net internal-web

# 确认容器现在同时出现在两个自定义网络中
docker inspect internal-web \
--format '{{json .NetworkSettings.Networks}}'

# 从第二个网络断开;不会停止容器
docker network disconnect extra-net internal-web

# 删除不再使用的第二个网络
docker network rm extra-net

实际项目中,前端代理可以同时加入 frontendbackend,数据库只加入 backend,以减少不必要的连通范围。

6. localhost 为什么经常用错

容器内的 127.0.0.1localhost 指向当前容器自身,不是宿主机,也不是另一个容器。

  • API 访问数据库:使用同一网络中的服务名,如 db:5432
  • 容器访问宿主机:Docker Desktop 通常可使用 host.docker.internal
  • Linux Engine:可在运行时添加 --add-host host.docker.internal:host-gateway
  • 宿主机访问容器:发布端口后使用 127.0.0.1:宿主机端口

Linux Engine 示例:

# 添加一个解析到宿主机网关的特殊名称,再显示解析结果
docker run --rm \
--add-host host.docker.internal:host-gateway \
alpine:3.20 \
getent hosts host.docker.internal

只有确实需要访问宿主机服务时才这样做。容器化依赖更适合放入同一个自定义网络。

7. 常见故障排查

端口已被占用

# macOS/Linux 查看谁正在监听 8080 端口
lsof -nP -iTCP:8080 -sTCP:LISTEN

# 查看是否已有 Docker 容器发布了该端口
docker ps --format 'table {{.Names}}\t{{.Ports}}'

停止冲突服务,或把映射改为 -p 8081:80。不要修改右边的容器端口,除非应用确实监听了另一个端口。

连接被拒绝

# 确认容器是否运行以及端口是否发布
docker ps -a --filter name=network-web
docker port network-web

# 查看应用是否因配置错误退出
docker logs --tail 50 network-web

# 在容器内部请求应用,区分“应用未监听”和“端口转发”问题
docker exec network-web \
wget -q -S -O /dev/null http://127.0.0.1:80

容器名无法解析

# 查看两个容器是否连接到同一个自定义网络
docker inspect internal-web \
--format '{{json .NetworkSettings.Networks}}'
docker network inspect beginner-net

# 从同一网络的临时容器测试 DNS
docker run --rm \
--network beginner-net \
alpine:3.20 \
getent hosts internal-web

默认 bridge 网络不提供与自定义 bridge 完全相同的名称解析体验。多容器应用应使用自定义网络或 Compose 默认网络。

8. 清理实验环境

# 删除两个 Nginx 实验容器
docker rm --force network-web internal-web

# 网络有容器连接时无法删除;清理容器后再删除
docker network rm beginner-net

# 确认实验对象已经不存在
docker ps -a --filter name=network-web
docker network ls --filter name=beginner-net

9. 本篇练习

  1. 在同一网络中启动两个 Nginx 容器,用临时 curl 容器分别按名称访问;
  2. 只给其中一个 Nginx 发布宿主机端口,说明另一个为何仍能被同网络容器访问;
  3. 把映射从 8080:80 改为 9090:80,验证容器内端口不变;
  4. docker network inspect 中找到网络网关和容器地址。

下一篇将学习容器数据为何会丢失,以及如何通过命名卷和 bind mount 持久化数据。