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
sudo mkdir -p /etc/systemd/system/docker.service.d
sudo 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-reload
sudo systemctl restart docker
# 验证
sudo systemctl show docker | grep -i proxy
2.3 取消代理
删掉 proxy.conf 再重启:
sudo rm /etc/systemd/system/docker.service.d/proxy.conf
sudo systemctl daemon-reload
sudo 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,::1
3.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.1
myapp_default: Subnet=172.18.0.0/16 Gateway=172.18.0.1
otherproject_default: Subnet=172.19.0.0/16 Gateway=172.19.0.1
bridge 才是 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/16
sudo 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 myimage
services:
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"
]
}
sudo systemctl restart docker
docker info | grep -A 10 "Registry Mirrors"
⚠️ 国内可用的镜像源经常被封 / 失效,建议配 3-5 个,失败自动 fallback。这条路适合只想拉镜像、不需要容器内程序走代理的场景。
八、调试代理是否生效
按“层级”排查:
8.1 daemon 代理生效了吗?
sudo systemctl show docker | grep -i proxy
8.2 镜像拉得动了吗?
docker pull hello-world
8.3 容器能访问宿主机吗?
docker run --rm -it alpine sh
# 进容器后
apk add curl
curl -v http://172.17.0.1:7890
8.4 容器内程序确实读到环境变量了吗?
docker exec myapp env | grep -i proxy
8.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 走不通”的问题,定位都是几分钟的事。