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://。

Docker容器通过HTTP和HTTPS代理访问外部网站与API
Docker 容器出站代理链路:容器内应用读取 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 等内部请求保持直连。不要只检查变量字符串是否存在。

Docker Compose中外部请求走代理而Redis和数据库通过NO_PROXY直连
Docker Compose 请求路径:app 访问外部 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
Docker容器直连与使用代理后的公网出口IP对比示意
Docker 容器出口验证示意:同一宿主机、同一接口,只改变容器代理配置。图中 IP 应使用文档示例地址,不代表真实测试结果。

正常使用独立代理出口时,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 容器已经正确走代理。