容器端口在 WSL2 里访问不到?Docker Desktop 的 network_mode:host 陷阱
· 阅读需 4 分钟
在为客户搭建 AI 数据分析平台(Airflow + PostgreSQL)的本地开发环境时遇到此问题,记录根因与解法。
TL;DR
Docker Desktop for Windows (WSL2 backend) 下,network_mode: host 的容器端口无法从 WSL2 宿主机访问。容器内 ss 显示端口在监听,但宿主机 curl localhost:PORT connection refused。原因是 host 模式共享的是 Docker 内部工具 VM 的网络,不是 WSL2 的网络。解法:override 文件中用 network_mode: !reset 移除 host 模式,改用 bridge + external network + 端口映射。
问题现象
项目使用 docker-compose.yml 部署 Airflow,base 配置:
x-airflow-common:
&airflow-common
image: airflow-ai-dag:latest
network_mode: host # 服务器上正常,WSL2 上出问题
volumes:
- ..:/opt/airflow/project
services:
airflow-webserver:
<<: *airflow-common
command: webserver
environment:
- AIRFLOW__WEBSERVER__WEB_SERVER_PORT=8082
容器启动正常,日志确认端口监听:
[INFO] Listening at: http://0.0.0.0:8082
但宿主机完全不可达:
$ ss -tlnp | grep 8082
# 空,无监听
$ curl localhost:8082/health
curl: (7) Failed to connect to localhost port 8082
而同一环境下,pgAdmin 容器用 -p 8080:80 端口映射,localhost:8080 正常访问。
根因
Docker Desktop for Windows 的网络架构与 Linux 原生 Docker 不同:
Windows 浏览器
↕
WSL2 宿主机(你操作的终端)
↕ Docker Desktop 管道
Docker Desktop 工具 VM ← network_mode: host 的 "host" 是这里
↕
容器
Linux 服务器上 network_mode: host 直接共享宿主机网络,容器端口 = 宿主机端口。但在 Docker Desktop WSL2 环境下:
- host 模式的 "host" = Docker Desktop 工具 VM,不是 WSL2
- 容器内
/proc/net/tcp有端口监听记录 - WSL2 宿主机的
/proc/net/tcp没有对应记录 -p端口映射的容器不受影响(Docker Desktop 会自动转发)
验证方法: 对比容器内外网络命名空间:
# 容器内:8082 (hex 1F92) 在监听
$ docker exec webserver grep '1F92' /proc/net/tcp
4: 00000000:1F92 00000000:0000 0A ...
# WSL2 宿主机:8082 不存在
$ grep '1F92' /proc/net/tcp
# 无输出
解决方案
步骤 1:创建 docker-compose.override.yml
用 !reset 移除 base 的 network_mode: host(需要 Compose v2.24+):
# docker-compose.override.yml
services:
airflow-webserver:
network_mode: !reset # 移除 base 的 host 模式
networks:
- app_network # 加入外部网络
ports:
- "8082:8082" # 端口映射
environment:
- DB_HOST=postgres-db # 用容器名连接数据库
airflow-scheduler:
network_mode: !reset
networks:
- app_network
environment:
- DB_HOST=postgres-db
networks:
app_network:
external: true # 引用已存在的外部网络
步骤 2:确保数据库容器在同一网络
# 查看数据库容器所在网络
$ docker inspect postgres-db --format '{{json .NetworkSettings.Networks}}'
# {"app_network": {...}}
# 确认外部网络存在(不存在则创建)
$ docker network create app_network # 或已存在则跳过
步骤 3:重启生效
$ docker compose down
$ docker compose up -d
验证:
$ ss -tlnp | grep 8082
LISTEN 0 4096 *:8082 *:*
$ curl -s -o /dev/null -w "%{http_code}" http://localhost:8082/health
200
关键点:!reset 必须放在服务定义里
以下写法不生效:
# ❌ 错误:放在 anchor 内
x-airflow-local:
&airflow-local
network_mode: !reset # 不生效!
networks:
- app_network
services:
airflow-webserver:
<<: *airflow-local # merge 时 base 的 network_mode:host 仍在
正确写法:
# ✅ 正确:每个服务单独写
services:
airflow-webserver:
network_mode: !reset # 必须在这里
networks:
- app_network
注意事项
network_mode: !reset需要 Docker Compose v2.24+,用docker compose version确认!reset只能移除标量字段(如network_mode),列表和字典字段不支持- 如果 base 使用了 YAML anchor(
<<: *anchor),!reset必须写在服务定义层,不能写在另一个 anchor 里 - 改为 bridge 模式后,容器间不能用
localhost互相访问,必须用容器名或加入同一 Docker network network_mode和networks互斥,同时存在会报mutually exclusive错误