网络规划的目标是减少暴露面,同时让容器之间的通信路径可解释。
\n
Docker 网络一开始看起来只是端口映射:左边宿主机端口,右边容器端口,能打开就行。服务多起来以后,问题会变复杂:容器互相访问用什么名字,哪些服务需要暴露给浏览器,哪些只能内部访问,反向代理出现 502 时到底是代理错了、上游挂了,还是网络不通。
我现在做网络规划,会先画“谁访问谁”,再写 ports。这一步能避免很多不必要的暴露。不是每个容器都需要宿主机端口,很多服务只需要被同一个 Docker 网络里的反向代理访问。
bridge 是默认选择
用户定义的 bridge 网络适合大多数 HomeLab Web 服务。它和默认 bridge 不一样,容器可以用服务名互相解析,网络边界也更清楚。比如 app 和 db 在同一个 back 网络里,应用连接数据库时用 db:5432,不需要写宿主机 IP。
networks:
front:
external: true
back:
driver: bridge
services:
app:
image: example/app:1.0.0
networks:
- front
- back
expose:
- "8080"
db:
image: postgres:16
networks:
- back这里 app 接入 front 是为了让反向代理访问,接入 back 是为了访问数据库;db 只在 back,不暴露宿主机端口。这样结构比所有服务都写 ports 清楚得多。
host 模式只给特殊场景
host 网络模式会让容器直接使用宿主机网络。它适合少数需要局域网广播、设备发现、特殊监听行为的服务,但不适合作为默认方案。用了 host,端口边界会变模糊,容器之间也少了一层 Docker 网络隔离。排查端口冲突时,你要同时看宿主机进程和容器进程。
我判断是否使用 host,会看服务是否真的依赖广播发现、mDNS、UPnP 或必须绑定宿主机网络。如果只是为了少写一个端口映射,那通常不值得。能用 bridge 加明确端口解决的,就先保持 bridge。
反代网络集中入口
反向代理的网络边界要简单:它只接入需要对外提供 HTTP 入口的服务。数据库、缓存、内部任务容器不要加入入口网络。这样即使代理配置错了,也不容易把内部组件暴露出去。
反代上游地址优先写服务名和容器内部端口,例如 http://app:8080,不要写宿主机映射端口。宿主机端口是给主机外部访问的,容器网络内部访问应该走服务名。很多 502 就是这里写混了:代理容器访问 127.0.0.1:18080,结果它访问的是代理容器自己,而不是宿主机上的服务。
502 排查从链路开始
遇到 502,我不会马上重启所有容器,而是沿请求链路拆开测:
docker network inspect front
docker compose exec proxy getent hosts app
docker compose exec proxy wget -S -O- http://app:8080/
docker compose logs --tail=120 proxy
docker compose logs --tail=120 app如果 getent hosts app 失败,说明服务名或网络有问题;如果能解析但 wget 失败,可能是应用没监听、端口写错或健康状态异常;如果代理访问成功但浏览器失败,再看 Host 头、协议、WebSocket 升级、证书或外部 DNS。
还要区分两条路径:从宿主机访问映射端口,和从代理容器访问上游服务名。前者失败多半和端口、防火墙、绑定地址有关;后者失败多半和 Docker 网络、服务名、内部端口有关。把这两条路径分开测,排障会快很多。
少暴露一点,文档清楚一点
网络规划不是为了复杂,而是为了让暴露面可控。我的原则是:浏览器直接访问的服务才映射端口;可以统一入口的服务放到反代后面;内部依赖只在内部网络通信;host 模式写明理由。最后把服务名、网络、入口端口和反代上游写进同一张表。等下一次出现 502 或端口冲突时,这张表比临时猜测可靠得多。
此处评论已关闭