摘要:端口映射是 iptables 的 DNAT 规则,容器按名互访靠自定义网络的嵌入式 DNS。本节拆解 -p 参数的三种形态与安全语义,解释默认网桥为什么没有 DNS,并给出生产环境的网络规划建议。
bridge 容器的 IP 是内网地址,外部访问必须"发布"端口:
# 形态一:发布到所有接口 docker run -d -p 8080:80 nginx:alpine # 形态二:只发布到指定接口 docker run -d -p 127.0.0.1:8080:80 nginx:alpine # 形态三:随机宿主机端口 docker run -d -P nginx:alpine
形态一把宿主机所有网卡的 8080 都指向容器 80——如果宿主机有公网 IP,这个端口就对全世界开门。形态二只绑定回环地址,仅供本机访问,测试环境与"只给本机反向代理用"的服务应当养成用它的习惯。-P 大写随机分配宿主机端口,docker port <容器> 可查分配结果。
规则落在哪?看 iptables:
sudo iptables -t nat -L DOCKER -n
输出里能找到一条 DNAT 规则,大意是目标地址宿主机 8080 端口的包,改写目标为容器 IP 的 80。也就是说端口映射不是代理进程转发(那会有额外开销),而是内核网络层的地址改写,路径极短。这也解释了一个现象:容器内的应用日志里记录的客户端 IP,看到的是 NAT 后的来源,若要真实客户端 IP 需要借助代理协议头之类机制。
试试在默认网桥上按名字找容器:
docker run -d --name web1 nginx:alpine docker run --rm alpine ping -c2 web1
ping 不通——默认网桥上容器名不解析,只能靠 IP。而 IP 是dhcp 式动态分配的,重启后可能变化,拿 IP 当服务地址等于埋雷。
创建自定义网络再试:
docker network create app-net docker run -d --name web2 --network app-net nginx:alpine docker run --rm --network app-net alpine ping -c2 web2
这次通了。Docker 为自定义网络内置了一个 DNS 服务器(127.0.0.11),同网络内的容器名、网络别名都会被解析。为什么默认网桥不给 DNS?历史与兼容原因:docker0 从早期就是全宿主机共享的一条街,所有容器挤在同一广播域,给它加全局 DNS 会牵动太多兼容性;而自定义网络按用户隔离,范围明确,DNS 也就顺理成章。结论很直接:生产上一律用自定义网络,默认网桥只留给临时调试容器。
网络管理的基本命令:
docker network ls docker network inspect app-net # 看网段、网关、已接入容器与 IP docker network connect app-net web1 # 运行中容器接入额外网络 docker network rm app-net
一个容器可以同时接入多个网络,--network 启动时指定一个,运行中可用 connect 加更多。多网络常用于隔离分层:前端网络连网关与应用,数据库网络只连应用与数据库,数据库容器不接入前端网络——网络分段就是最朴素的安全边界。
以一个典型三层应用(nginx 网关 + 应用 + 数据库)为例:
docker network create front-net docker network create back-net docker run -d --name db --network back-net postgres:16 docker run -d --name app --network back-net --network front-net myapp docker run -d --name gw --network front-net -p 80:80 nginx:alpine
要点:只有网关发布端口到宿主机,且生产应只发布到指定内网接口或交给前置的负载均衡;应用跨两个网络,分别面向网关与数据库;数据库不发布任何端口,只被 back-net 内按名访问。改用 IP、发布 5432 到公网、让数据库跟网关挤同一个网络,都是生产上见过太多次的反面教材。

容器互 ping 得通但业务连不上:DNS 与网络都正常,多半是目标服务监听了 127.0.0.1(容器内回环),改为 0.0.0.0 监听即可。
同网络容器名解析失败:确认双方真在同一个自定义网络(inspect 看 Containers 列表);默认网桥上的老容器 connect 进新网络后,老容器本身仍解析不了别人。
发布端口被占用:bind: address already in use,宿主机上已有进程占了该端口,换端口或查占用(ss -lntp)。host 模式下同理但更隐蔽,因为不经过 docker 代理,报错来自应用自身。
网络讲完,下一章进入存储层——数据在容器世界里到底住在哪。