Docker 网络与端口转发
容器之间如何互相看到,发布端口与简单的 EXPOSE 有何不同,以及为什么 Docker 会绕过 ufw 规则。
更新于 2026年8月23日
容器问题有一半都和网络有关:服务连不上数据库、端口被占用、控制面板突然暴露在整个互联网上。下面我们来拆解一下它的工作原理。
三种网络类型
- bridge —— 默认类型。容器获得内部地址并相互通信;它们通过宿主机的 NAT 访问外部。
- host —— 容器直接使用宿主机的网络;端口映射既不需要也不起作用。速度快,但没有隔离:容器的 80 端口就是宿主机的 80 端口。
- none —— 完全没有网络。适用于不需要网络的任务。
由您或 Compose 创建的自定义网络与默认的 bridge 有一个重要区别:在其中名称解析可以正常工作。app 容器可以通过名称 db 访问 db 容器。
docker network create backend
docker network ls
docker network inspect backend
在 Compose 中,这样的网络会为每个项目自动创建。
端口映射
ports:
- "127.0.0.1:8080:8080" # accessible only from the machine itself
- "80:80" # accessible to the whole internet
左边是宿主机上的端口,右边是容器内部的端口。镜像中的 EXPOSE 指令不会映射任何端口:它只是说明应用程序监听哪个端口。
规则很简单:只映射必须从外部访问的端口。数据库、控制面板和内部 API 绑定到 127.0.0.1,而带证书的 Web 服务器则将它们暴露到外部。
Docker 会自行写入
iptables规则,而且是在ufw管理的链之前写入。以-p 8080:8080映射的端口即使ufw显示“deny”,也能从互联网访问。不要依赖防火墙——请将端口绑定到127.0.0.1。
容器之间的通信
services:
app:
networks: [frontend, backend]
db:
networks: [backend]
proxy:
networks: [frontend]
ports:
- "80:80"
- "443:443"
networks:
frontend:
backend:
在这里,数据库只对应用程序可访问,只有 Web 服务器面向外部。这是任何技术栈的正确配置。
在容器内部,通过服务名称访问相邻容器:postgres://db:5432/app。容器内的 localhost 地址指的是容器本身,而不是宿主机——这是仅次于 latest 的第二常见错误。
要从容器访问运行在宿主机本身上的服务,请使用名称 host.docker.internal,并添加:
extra_hosts:
- "host.docker.internal:host-gateway"
诊断
docker compose exec app ping -c2 db
docker compose exec app getent hosts db
sudo ss -tulpn | grep LISTEN
docker port <container>
ss 会显示端口实际监听在哪个接口上:0.0.0.0:8080 —— 对外开放,127.0.0.1:8080 —— 不对外开放。
常见错误
- “端口已被占用”。 它被另一个容器或宿主机服务占用。
sudo ss -tulpn | grep :80可以找出罪魁祸首。 - 不同 Compose 项目中的容器互相看不到。 每个项目都有自己的网络。请通过
networks: { shared: { external: true } }显式连接它们。 - 应用程序在容器内监听
127.0.0.1。 那么端口映射也无济于事:您无法从容器外部访问它。请将应用程序配置为监听0.0.0.0。 - 带宽。 每台宿主机共享最高 500 Mbps。文件分发会与其他所有流量共享它。