Docker 设置代理不是一个统一开关:docker pull、docker build 和运行中的容器访问外网,属于三种不同网络场景。本文重点解决第三种——让运行中的 Docker 容器应用通过 HTTP/HTTPS 代理访问外部服务,同时讲清 Docker Compose、NO_PROXY、默认代理配置、认证与出口验证。
| 你的问题 | 应该配置哪里 | 本文是否重点讲 |
|---|---|---|
docker pull 拉不到镜像 |
Docker daemon | 只说明边界 |
docker build 下载依赖失败 |
Build proxy | 简要说明 |
| 容器内应用访问外网需要代理 | HTTP_PROXY / HTTPS_PROXY |
重点 |
| Docker Compose 某个服务走代理 | environment / env_file |
重点 |
| 内部 Redis、DB 不应该走代理 | NO_PROXY |
重点 |
一、Docker 设置代理前,先分清 daemon、build 和 container
很多排查之所以反复无效,是因为配置对象选错了:容器访问 API 失败,却一直修改 daemon;或者 docker pull 失败,却只给业务容器注入 HTTP_PROXY。开始修改配置前,先确认请求究竟从哪一层发出。
Docker daemon:访问 Registry 与 Docker Hub
Docker daemon 需要访问 Docker Hub、其他 Registry 或 Swarm 节点时,应配置 daemon 代理。Docker Engine 可以在 daemon.json 的 proxies 对象中设置 http-proxy、https-proxy 与 no-proxy;修改后需要重启 daemon 才会生效。具体格式见 Docker daemon 代理官方文档。
Docker Desktop 是一个需要单独注意的边界:官方文档明确说明,Docker Desktop 会忽略 daemon.json 中的代理配置,应在 Docker Desktop 设置中配置代理。本文不继续展开 Desktop 或 systemd 的 daemon 设置。
Docker build:构建阶段下载依赖
RUN apt update、RUN npm install 或 RUN pip install 发生在构建阶段。需要代理时,应使用 Docker 支持的 build arguments 或客户端默认代理配置,不要通过 Dockerfile 的 ENV 把代理永久写进镜像。这样既避免镜像换环境后继续指向旧代理,也减少凭证进入镜像历史或配置的风险。
Running container:业务应用访问外网
运行中的 Python、Node.js、cURL 或其他业务客户端访问外部服务时,常见入口是容器环境里的 HTTP_PROXY、HTTPS_PROXY 与 NO_PROXY。这才是本文后续章节的重点。
daemon.json 中的 daemon/Registry 代理,与运行中容器应用的出站代理不是同一个问题;Docker Hub 镜像加速也不属于本文范围。
二、Docker 容器怎么走代理?设置 HTTP_PROXY 和 HTTPS_PROXY
单次启动容器时,可以通过 -e 或 --env 把代理配置放进容器环境。下面是最小示例:
docker run --rm \
-e HTTP_PROXY="http://proxy.example.com:7778" \
-e HTTPS_PROXY="http://proxy.example.com:7778" \
-e NO_PROXY="localhost,127.0.0.1,.internal.example.com" \
my-app
| 变量 | 作用 | 容易误解的地方 |
|---|---|---|
HTTP_PROXY |
供应用处理 HTTP 目标请求时选择代理 | 是否读取取决于容器内应用或 HTTP Client |
HTTPS_PROXY |
供应用处理 HTTPS 目标请求时选择代理 | 变量名不代表代理端点必须是 https:// |
NO_PROXY |
列出应直接连接的目标 | 不同客户端对格式和匹配规则的实现可能不同 |
把 HTTP_PROXY 放进容器环境,不等于容器中的每个程序都会自动走代理。Docker 官方指出,目前没有统一标准规定所有 Web Client 应如何处理代理环境变量及其格式。有些客户端读取大写,有些读取小写,有些需要应用内显式设置;遇到出口不变时,应先检查实际使用的语言库和 HTTP Client。
如果目标应用只识别小写变量,可按其文档使用或同时注入 http_proxy、https_proxy 和 no_proxy。通过 ~/.docker/config.json 设置默认代理时,Docker 会同时注入对应的大写和小写变量。
HTTPS_PROXY="http://proxy.example.com:7778" 并不矛盾:变量名中的 HTTPS 描述目标请求类型,值开头的 http:// 描述客户端连接的代理端点协议。只有服务商明确提供 HTTPS Proxy 端点时,才应按其要求填写 https://。
HTTP_PROXY / HTTPS_PROXY 后,经代理服务器访问外部网站或 API。三、Docker Compose 代理怎么配置?
Compose 中可以用 environment 给指定服务注入代理变量。下面只有 app 需要访问外部 API;redis 是同一 Compose 网络中的内部服务:
services:
app:
image: my-app
environment:
HTTP_PROXY: ${HTTP_PROXY}
HTTPS_PROXY: ${HTTPS_PROXY}
NO_PROXY: localhost,127.0.0.1,redis,db,.internal.example.com
depends_on:
- redis
redis:
image: redis:7
在启动 Compose 前,由 Shell、项目 .env 或 --env-file 提供 HTTP_PROXY 和 HTTPS_PROXY。可以先运行 docker compose config 查看变量替换后的配置,但输出可能包含代理地址或凭证,不要直接复制到公开日志或工单。
如果变量较多,也可以使用 env_file:
services:
app:
image: my-app
env_file:
- ./proxy.env
environment 和 env_file 都是在容器环境中设置变量,不会让不支持代理环境变量的应用突然获得代理能力。Docker 的 Compose 环境变量官方说明也提醒,不应把密码等敏感信息简单当作普通环境变量管理;如果应用支持从文件读取凭证,可考虑使用 Compose secrets 并由应用在运行时构造代理配置。
四、怎么用 ~/.docker/config.json 给新容器设置默认代理?
如果每次 docker run 都重复填写代理变量,可以在 Docker 客户端配置文件 ~/.docker/config.json 中设置默认代理:
{
"proxies": {
"default": {
"httpProxy": "http://proxy.example.com:7778",
"httpsProxy": "http://proxy.example.com:7778",
"noProxy": "localhost,127.0.0.1,.internal.example.com"
}
}
}
这里最容易混淆的不是 JSON 格式,而是文件的作用对象:
- 文件位置是客户端的
~/.docker/config.json,不是 daemon 的/etc/docker/daemon.json。 httpProxy、httpsProxy和noProxy会为新容器设置对应的大小写代理环境变量,也会作为构建参数提供给新 build。- 保存配置后无需重启 Docker,但只影响之后创建的容器和构建,旧容器不会自动变化。
- 它不是 Docker CLI 或 Docker Engine 自身访问网络时使用的代理设置。
这些行为可在 Docker CLI 容器代理官方文档中核对。因为代理 URL 可能包含凭证或内部地址,配置文件本身也应限制访问权限。
五、NO_PROXY 怎么配置?哪些内部服务不走代理?
在 Compose 默认网络中,服务通常通过服务名互相访问,例如 app → redis、app → db。这些内部请求通常不需要先发往外部代理,因此可把内部服务名、回环地址和内部域名加入 NO_PROXY:
NO_PROXY="localhost,127.0.0.1,redis,db,.internal.example.com"
这里有两个边界需要同时记住:
redis和db是 Docker 网络里的服务名;容器内的localhost只指当前容器自身,不能代表另一个服务。NO_PROXY缺少所有客户端统一遵循的标准。域名后缀、端口、CIDR、大小写变量等匹配行为可能因语言和 HTTP Client 而不同。
因此,配置完成后应分别测试一个外部 API 和一个内部服务,确认外部请求经过代理、Redis/DB 等内部请求保持直连。不要只检查变量字符串是否存在。
NO_PROXY 保持直连。六、Docker 代理需要账号密码怎么处理?
部分 HTTP 代理使用包含凭证的 URL:
http://username:password@proxy.example.com:7778
如果 Username 或 Password 含有 @、:、#、% 等 URL 特殊字符,应只对凭证字段做 URL 编码,再拼入代理 URL;不要对整条 URL 统一编码,也不要把编码后的值重复编码。遇到 407 时,还要核对认证方式以及服务商侧的来源 IP 白名单、地区或 Session 用户名参数。完整路径可参考 407 代理认证排查指南。
不要把代理凭证固化进镜像
不要在 Dockerfile 里用 ENV HTTP_PROXY=... 永久写入代理地址和密码。Docker 官方明确提醒,容器环境变量会以明文形式存入容器配置,可通过远程 API 等方式查看;通过 docker commit 还可能进入镜像。构建阶段的代理配置优先使用 Docker 预定义的 proxy build arguments,不要通过 Dockerfile ENV 固化代理;如果构建步骤还需要读取其他敏感凭证,再根据具体工具使用 BuildKit secrets 等机制。运行阶段则根据应用能力使用 Secret 文件或受控的变量注入方式。
即使使用 .env 或 env_file,也不代表凭证已被加密。至少应避免提交到 Git、限制文件权限,并在日志、docker inspect 输出和工单截图中脱敏。
七、怎么验证 Docker 容器真的经过代理?
先确认变量是否进入容器
docker exec my-app sh -c 'env | grep -i proxy'
如果镜像没有 Shell 或 grep,可使用 docker inspect 查看容器配置,或由应用输出经过脱敏的启动配置。不要在共享日志里打印带账号密码的完整代理 URL。
看到 HTTP_PROXY 只证明配置被注入,不代表应用已经读取它,更不代表公网出口已经改变。
再用实际请求比较公网出口
在同一宿主机、同一查询接口下,分别启动不带代理变量和带代理变量的诊断容器。以下示例假设本地可以取得 curlimages/curl 镜像;镜像拉取本身属于 daemon 网络问题,不要和容器运行时验证混在一起。
# Direct
docker run --rm curlimages/curl \
-s https://api.ipify.org
# Via Docker Proxy
docker run --rm \
-e HTTP_PROXY="http://proxy.example.com:7778" \
-e HTTPS_PROXY="http://proxy.example.com:7778" \
curlimages/curl \
-s https://api.ipify.org
正常使用独立代理出口时,Via Proxy 返回的公网 IP 通常应与 Direct 不同。如果配置了指定地区,再用同一个 GEO 数据来源核对国家、城市等结果。还分不清宿主机地址、容器地址与公网出口时,可参考站内的 本地 IP 与公网出口判断方法。
如果业务需要较长时间保持相对固定的公网出口,可根据任务评估 静态ISP IP。本文只验证 Docker 代理配置是否生效,不展开代理类型选择和轮换策略。
八、Docker 设置代理后为什么不生效?
排查时先确定失败发生在 daemon、build 还是 running container,再记录容器实际环境、应用使用的 HTTP Client、目标 Host 和出口 IP。不要同时修改三层配置。
| 现象 | 优先排查 | 处理方向 |
|---|---|---|
| 宿主机走代理,容器还是直连 | 容器是否注入代理变量 | 不要假设容器自动继承宿主机所有代理设置 |
env 能看到代理,但出口没变 |
应用是否读取这些变量 | 查看 HTTP Client 文档,并做实际公网 IP 测试 |
| Redis / DB 访问异常 | NO_PROXY 是否包含服务名 |
分别测试内部直连和外部代理请求 |
改了 config.json,旧容器没变化 |
容器创建时间 | 重新创建容器,不只执行 restart |
docker pull 仍然失败 |
daemon / Docker Desktop 代理 | 不要继续修改 container proxy |
| 407 | 凭证、URL 编码、认证方式与服务商规则 | 先用最小端点测试,再转入 407 专项排查 |
| HTTPS / TLS 错误 | 代理端点协议、目标证书、CA / TLS 链路 | 判断错误发生在客户端到代理还是代理到目标 |
127.0.0.1:7890 代理无法连接 |
代理实际运行位置与监听地址 | 容器中的 localhost 是容器自己,不是宿主机 |
宿主机代理为什么不能直接写 127.0.0.1?
容器拥有自己的网络命名空间,容器中的 127.0.0.1 指向该容器自身。如果代理程序运行在宿主机,应使用当前平台提供的宿主机访问方式,并确认代理监听在容器可达的接口、防火墙允许连接。
Docker Desktop 通常提供 host.docker.internal;原生 Linux Docker Engine 的行为与版本、网络模式有关,可按环境使用 --add-host=host.docker.internal:host-gateway 或明确的宿主机网关地址。不要把一种平台的写法直接当成所有环境通用答案。
为什么 restart 不够?
容器环境变量在创建容器时确定。修改 Compose、docker run 参数或 ~/.docker/config.json 后,仅重启旧容器不会重新生成它的环境配置。Compose 场景可先用 docker compose config 核对解析结果,再重新创建相关服务。
九、Docker 设置代理常见问题
1. Docker 设置代理和 Docker Hub 镜像加速一样吗?
不一样。代理决定请求是否经由代理服务器转发;镜像加速通常涉及 Registry 镜像源或缓存服务。运行中容器的 HTTP_PROXY 更不会自动解决 docker pull 的 daemon 网络问题。
2. Docker Compose 设置 HTTP_PROXY 后,所有程序都会走代理吗?
不一定。Compose 只把变量放进容器环境,最终是否读取、读取大写还是小写、如何解释 NO_PROXY,取决于应用和 HTTP Client。应通过实际出口请求验证。
3. 修改 ~/.docker/config.json 后,为什么旧容器没有变化?
该配置只影响之后创建的新容器和构建。旧容器的环境不会被自动改写,需要重新创建容器。
4. HTTPS_PROXY 的值一定必须是 https:// 吗?
不一定。变量名描述要代理的 HTTPS 目标请求,URL scheme 描述实际代理端点协议。服务商提供的是 HTTP 代理端点时,值仍可能以 http:// 开头。
5. 为什么宿主机的 127.0.0.1 代理在容器里不能用?
容器中的 127.0.0.1 指向容器自己,而不是宿主机。应根据 Docker Desktop 或 Linux Engine 的实际网络方式访问宿主机,并确认代理监听地址与防火墙允许容器连接。
最终验收至少确认四项:配置作用在正确层级、代理变量进入了新容器、内部服务按 NO_PROXY 保持直连、外部请求的公网出口符合预期。变量存在或命令没有报错,都不能单独证明 Docker 容器已经正确走代理。