Docker 走代理的 3 种姿势:daemon / Compose / host 网络
Docker 走代理是个看似简单实则坑很深的话题,因为它有三个完全独立的层级:
docker pull拉镜像走代理(Docker daemon 的代理)- 容器内程序自己走代理(容器进程的环境变量)
- Compose 跨服务怎么访问宿主机代理
每个层级的配置位置都不一样,搞混了排查能查一天。这篇按这三个层级分别讲。
一、整体关系#
| 层级 | 配置位置 | 影响 |
|---|---|---|
| Daemon | /etc/systemd/system/docker.service.d/proxy.conf | 拉镜像 |
| 容器构建(build) | ~/.docker/config.json 或 --build-arg | docker build 时容器内的网络请求 |
| 容器运行(run) | -e HTTP_PROXY=... 或 environment: | 容器进程的 HTTP 客户端 |
二、场景一:让 docker pull 走代理#
国内服务器拉 Docker Hub 经常超时,最干净的方案不是改镜像源,而是让 daemon 走你的代理。
2.1 创建 systemd drop-in#
1sudo mkdir -p /etc/systemd/system/docker.service.d2sudo vim /etc/systemd/system/docker.service.d/proxy.conf内容:
1[Service]2Environment="HTTP_PROXY=http://127.0.0.1:7890"3Environment="HTTPS_PROXY=http://127.0.0.1:7890"4Environment="NO_PROXY=localhost,127.0.0.1,::1,*.aliyun.com,*.tencentyun.com"💡
NO_PROXY把国内常用的镜像域名加进去,避免走代理反而慢。
2.2 重启 Docker#
1sudo systemctl daemon-reload2sudo systemctl restart docker3
4# 验证5sudo systemctl show docker | grep -i proxy2.3 取消代理#
删掉 proxy.conf 再重启:
1sudo rm /etc/systemd/system/docker.service.d/proxy.conf2sudo systemctl daemon-reload3sudo systemctl restart docker🪤 这个配置只影响
docker pull/docker push,不影响容器内部进程。容器要走代理需要单独配置(场景二)。
三、场景二:让容器内程序走代理#
最常见的需求:容器跑了个程序要访问 GitHub / OpenAI,但你的代理在宿主机上。
3.1 简单粗暴:-e 传环境变量#
1docker run -d \2 -e HTTP_PROXY=http://172.17.0.1:7890 \3 -e HTTPS_PROXY=http://172.17.0.1:7890 \4 -e NO_PROXY=localhost,127.0.0.1,::1 \5 myimage注意:用宿主机 IP 172.17.0.1(默认 bridge 网关),不要写 127.0.0.1(那是容器自己)。
3.2 Compose 写法#
1services:2 myapp:3 image: myimage4 environment:5 HTTP_PROXY: http://172.17.0.1:78906 HTTPS_PROXY: http://172.17.0.1:78907 NO_PROXY: localhost,127.0.0.1,::13.3 ~/.docker/config.json(构建时也生效)#
1{2 "proxies": {3 "default": {4 "httpProxy": "http://172.17.0.1:7890",5 "httpsProxy": "http://172.17.0.1:7890",6 "noProxy": "localhost,127.0.0.1,::1"7 }8 }9}这种方式对所有 docker build 和 docker run 都自动生效,省去每次写 -e。
⚠️ 程序必须遵守
HTTP_PROXY环境变量才会走代理。比如curl/wget/ Python 的requests/ Java 都遵守;但有些程序不读这个变量(如 Go 自己实现的 HTTP 客户端、部分 nodejs 库),那就只能在程序代码里改。
四、场景三:宿主机代理在 Compose 网络中”找不到”#
这是 Compose 用户最高频的坑:
用
host.docker.internal在 Compose 容器里根本访问不到宿主机代理。
4.1 为什么 host.docker.internal 不行?#
1# 这种写法在 Compose 里基本不工作2services:3 app:4 image: myimage5 extra_hosts:6 - "host.docker.internal:host-gateway"原理:
host-gateway是 Docker 的魔法关键字,会被解析成默认 bridge 网桥的网关 IP172.17.0.1。- 但 Compose 会为每个项目创建独立网络,网关 IP 是
172.18.0.1/172.19.0.1等,和172.17.0.1不在同一段。 - 结果:容器把
host.docker.internal解析成172.17.0.1,但自己网络段是172.18.0.0/16,根本到不了172.17.0.1。
4.2 查看你 Docker 上所有网关 IP#
1docker network inspect $(docker network ls -q) \2 --format '{{.Name}}: Subnet={{(index .IPAM.Config 0).Subnet}} Gateway={{(index .IPAM.Config 0).Gateway}}'输出示例:
1bridge: Subnet=172.17.0.0/16 Gateway=172.17.0.12myapp_default: Subnet=172.18.0.0/16 Gateway=172.18.0.13otherproject_default: Subnet=172.19.0.0/16 Gateway=172.19.0.1bridge 才是 Docker 默认的;其它都是 Compose 自动创建的。
4.3 正确做法:手动指定 Compose 网络段 + 网关#
1services:2 myapp:3 image: myimage4 extra_hosts:5 - "host.docker.internal:172.30.0.1" # 指向下面定义的网关6 environment:7 HTTP_PROXY: http://host.docker.internal:78908 HTTPS_PROXY: http://host.docker.internal:78909
10networks:11 default:12 ipam:13 config:14 - subnet: 172.30.0.0/1615 gateway: 172.30.0.1关键点:
- 自定义网段和网关(
172.30.0.0/16,挑一个没被占用的) extra_hosts写死这个网关 IP,不要用host-gateway魔法值- 容器里访问
host.docker.internal→172.30.0.1→ 宿主机
4.4 必备:宿主机防火墙放开这个网段#
⚠️ 重要:默认 ufw 会拦截除 127.0.0.1 外所有源(含 Docker 网段)。容器请求宿主机代理会被防火墙挡掉。
1sudo ufw allow from 172.30.0.0/162sudo ufw reload或者代理软件层面允许 LAN(如 mihomo 的 allow-lan: true 配合 bind-address: "*")。
4.5 验证容器内能否解析 + 连通#
1docker exec -it myapp sh2
3# 解析4getent ahosts host.docker.internal5# 应该显示 172.30.0.16
7# 连通8nc -zv host.docker.internal 78909# 应该 succeeded五、environment 代理 vs 应用配置文件代理#
很多人发现:明明 environment 写了 HTTP_PROXY,应用的某些请求还是没走代理——这是因为两者作用层级不同:
| 层级 | 影响范围 |
|---|---|
environment 的 HTTP_PROXY | 进程级,影响通用 HTTP 库(curl、Python requests、Java HttpClient 等) |
应用自己的 config.yaml 里的 proxy | 业务级,应用特定 API 走的代理 |
举例:某个 AI 网关程序
environment: HTTP_PROXY=...→ 控制下载管理面板、访问 GitHub API 等”框架请求”config.yaml里的 proxy → 控制转发到 OpenAI 的”业务请求”
两个都要配,不能互相覆盖。
六、host 网络模式:最简单粗暴的方案#
如果你不想折腾网络段,直接让容器和宿主机共享网络栈:
1docker run -d --network host myimage1services:2 myapp:3 image: myimage4 network_mode: host5 environment:6 HTTP_PROXY: http://127.0.0.1:7890 # 直接写 127.0.0.17 HTTPS_PROXY: http://127.0.0.1:7890优点:
127.0.0.1就是宿主机的127.0.0.1- 不用映射端口(
-p失效) - 性能最好(无 NAT)
缺点:
- 失去网络隔离(容器能看到宿主机所有端口)
- 多个容器端口冲突(不能再有两个监听 80 的容器)
- 不能用 Compose 的容器名 DNS(host 模式没有 Compose 网络)
适合:单机部署、性能敏感、不需要多容器互联的场景。
七、Docker 镜像加速(拉镜像的另一条路)#
如果不想配代理,国内可以用镜像加速器:
1sudo vim /etc/docker/daemon.json1{2 "registry-mirrors": [3 "https://docker.1ms.run",4 "https://hub.fast360.xyz",5 "https://docker.hpcloud.cloud"6 ]7}1sudo systemctl restart docker2docker info | grep -A 10 "Registry Mirrors"⚠️ 国内可用的镜像源经常被封 / 失效,建议配 3-5 个,失败自动 fallback。这条路适合只想拉镜像、不需要容器内程序走代理的场景。
八、调试代理是否生效#
按”层级”排查:
8.1 daemon 代理生效了吗?#
1sudo systemctl show docker | grep -i proxy8.2 镜像拉得动了吗?#
1docker pull hello-world8.3 容器能访问宿主机吗?#
1docker run --rm -it alpine sh2# 进容器后3apk add curl4curl -v http://172.17.0.1:78908.4 容器内程序确实读到环境变量了吗?#
1docker exec myapp env | grep -i proxy8.5 程序是否遵守环境变量?#
1docker exec myapp curl -v https://www.google.com2# 如果走代理,会看到 Proxy-Connection 之类的头九、踩坑速查表#
| 现象 | 原因 | 解决 |
|---|---|---|
docker pull 慢 | daemon 没配代理 | 配 /etc/systemd/system/docker.service.d/proxy.conf |
| 配了 daemon 代理,容器还是慢 | 容器不读 daemon 代理 | 容器需单独 -e HTTP_PROXY |
| 容器内 curl 走代理失败 | ufw 挡了 Docker 网段 | ufw allow from <Docker 网段> |
Compose 用 host.docker.internal 不通 | 跨网段问题 | 手动指定网段 + 网关 |
host-gateway 解析成 172.17.0.1 但不通 | Compose 网络段不是 172.17 | 用上面的”指定网关”方案 |
| 环境变量配了程序还是没代理 | 程序不读 HTTP_PROXY | 在程序代码里改 |
十、几条实用经验#
- 能用宿主机代理就别在容器里再装 mihomo——多套一层全是麻烦。
172.17.0.1只在默认 bridge 网络下可用,Compose 自定义网络要算自己的网关。- 代理 + 防火墙是组合拳:代理通了但 ufw 挡了网段,照样不通。
docker exec ... env是排查”环境变量是否生效”的第一招。- 混用 daemon 代理 + 容器代理没关系,分别管不同事。
把这三个层级理清楚,后面再遇到任何”Docker 走不通”的问题,定位都是几分钟的事。