Docker 走代理的 3 种姿势:daemon / Compose / host 网络
Docker 走代理是个看似简单实则坑很深的话题,因为它有三个完全独立的层级:
docker pull拉镜像走代理(Docker daemon 的代理)- 容器内程序自己走代理(容器进程的环境变量)
- Compose 跨服务怎么访问宿主机代理
每个层级的配置位置都不一样,搞混了排查能查一天。这篇按这三个层级分别讲。
一、整体关系
跑着 mihomo 7890 端口] H --> D[Docker daemon
拉镜像走代理] H --> C[容器内进程
访问外网走代理] D --> R[Docker Hub
GHCR 等 Registry] C --> X[各种 API / 外网]
D -.读.-> S[/etc/systemd/system/docker.service.d/proxy.conf] C -.读.-> E1[容器 environment 环境变量] C -.或读.-> E2[~/.docker/config.json]
| 层级 | 配置位置 | 影响 |
|---|---|---|
| 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
sudo mkdir -p /etc/systemd/system/docker.service.dsudo vim /etc/systemd/system/docker.service.d/proxy.conf内容:
[Service]Environment="HTTP_PROXY=http://127.0.0.1:7890"Environment="HTTPS_PROXY=http://127.0.0.1:7890"Environment="NO_PROXY=localhost,127.0.0.1,::1,*.aliyun.com,*.tencentyun.com"💡
NO_PROXY把国内常用的镜像域名加进去,避免走代理反而慢。
2.2 重启 Docker
sudo systemctl daemon-reloadsudo systemctl restart docker
# 验证sudo systemctl show docker | grep -i proxy2.3 取消代理
删掉 proxy.conf 再重启:
sudo rm /etc/systemd/system/docker.service.d/proxy.confsudo systemctl daemon-reloadsudo systemctl restart docker🪤 这个配置只影响
docker pull/docker push,不影响容器内部进程。容器要走代理需要单独配置(场景二)。
三、场景二:让容器内程序走代理
最常见的需求:容器跑了个程序要访问 GitHub / OpenAI,但你的代理在宿主机上。
3.1 简单粗暴:-e 传环境变量
docker run -d \ -e HTTP_PROXY=http://172.17.0.1:7890 \ -e HTTPS_PROXY=http://172.17.0.1:7890 \ -e NO_PROXY=localhost,127.0.0.1,::1 \ myimage注意:用宿主机 IP 172.17.0.1(默认 bridge 网关),不要写 127.0.0.1(那是容器自己)。
3.2 Compose 写法
services: myapp: image: myimage environment: HTTP_PROXY: http://172.17.0.1:7890 HTTPS_PROXY: http://172.17.0.1:7890 NO_PROXY: localhost,127.0.0.1,::13.3 ~/.docker/config.json(构建时也生效)
{ "proxies": { "default": { "httpProxy": "http://172.17.0.1:7890", "httpsProxy": "http://172.17.0.1:7890", "noProxy": "localhost,127.0.0.1,::1" } }}这种方式对所有 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 不行?
# 这种写法在 Compose 里基本不工作services: app: image: myimage extra_hosts: - "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
docker network inspect $(docker network ls -q) \ --format '{{.Name}}: Subnet={{(index .IPAM.Config 0).Subnet}} Gateway={{(index .IPAM.Config 0).Gateway}}'输出示例:
bridge: Subnet=172.17.0.0/16 Gateway=172.17.0.1myapp_default: Subnet=172.18.0.0/16 Gateway=172.18.0.1otherproject_default: Subnet=172.19.0.0/16 Gateway=172.19.0.1bridge 才是 Docker 默认的;其它都是 Compose 自动创建的。
4.3 正确做法:手动指定 Compose 网络段 + 网关
services: myapp: image: myimage extra_hosts: - "host.docker.internal:172.30.0.1" # 指向下面定义的网关 environment: HTTP_PROXY: http://host.docker.internal:7890 HTTPS_PROXY: http://host.docker.internal:7890
networks: default: ipam: config: - subnet: 172.30.0.0/16 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 网段)。容器请求宿主机代理会被防火墙挡掉。
sudo ufw allow from 172.30.0.0/16sudo ufw reload或者代理软件层面允许 LAN(如 mihomo 的 allow-lan: true 配合 bind-address: "*")。
4.5 验证容器内能否解析 + 连通
docker exec -it myapp sh
# 解析getent ahosts host.docker.internal# 应该显示 172.30.0.1
# 连通nc -zv host.docker.internal 7890# 应该 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 网络模式:最简单粗暴的方案
如果你不想折腾网络段,直接让容器和宿主机共享网络栈:
docker run -d --network host myimageservices: myapp: image: myimage network_mode: host environment: HTTP_PROXY: http://127.0.0.1:7890 # 直接写 127.0.0.1 HTTPS_PROXY: http://127.0.0.1:7890优点:
127.0.0.1就是宿主机的127.0.0.1- 不用映射端口(
-p失效) - 性能最好(无 NAT)
缺点:
- 失去网络隔离(容器能看到宿主机所有端口)
- 多个容器端口冲突(不能再有两个监听 80 的容器)
- 不能用 Compose 的容器名 DNS(host 模式没有 Compose 网络)
适合:单机部署、性能敏感、不需要多容器互联的场景。
七、Docker 镜像加速(拉镜像的另一条路)
如果不想配代理,国内可以用镜像加速器:
sudo vim /etc/docker/daemon.json{ "registry-mirrors": [ "https://docker.1ms.run", "https://hub.fast360.xyz", "https://docker.hpcloud.cloud" ]}sudo systemctl restart dockerdocker info | grep -A 10 "Registry Mirrors"⚠️ 国内可用的镜像源经常被封 / 失效,建议配 3-5 个,失败自动 fallback。这条路适合只想拉镜像、不需要容器内程序走代理的场景。
八、调试代理是否生效
按”层级”排查:
8.1 daemon 代理生效了吗?
sudo systemctl show docker | grep -i proxy8.2 镜像拉得动了吗?
docker pull hello-world8.3 容器能访问宿主机吗?
docker run --rm -it alpine sh# 进容器后apk add curlcurl -v http://172.17.0.1:78908.4 容器内程序确实读到环境变量了吗?
docker exec myapp env | grep -i proxy8.5 程序是否遵守环境变量?
docker exec myapp curl -v https://www.google.com# 如果走代理,会看到 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 走不通”的问题,定位都是几分钟的事。