您是在公司网络中使用Docker,还是在访问受限的服务器上,或者希望您的容器通过特定的IP访问互联网?没有正确配置的代理,Docker将无法从Docker Hub或任何其他注册表下载镜像。在本文中,我们将讨论所有级别的代理设置——从守护进程到单独的容器。
Docker为什么需要代理
Docker是一个不断访问外部资源的工具。在每次docker pull或docker build时,守护进程会访问Docker Hub、GitHub Container Registry、Google Container Registry或您的私有注册表。这时问题就出现了。
没有代理无法避免的情况:
- 公司网络中的防火墙——所有流量必须通过公司代理服务器,否则连接将被阻止。
- 地理限制——Docker Hub或某些注册表在您的国家或数据中心不可用。
- 没有直接互联网访问的受限服务器——VPS在封闭网络中,互联网只能通过网关访问。
- 控制容器的出站流量——您希望容器内的应用程序通过特定IP访问网络,例如用于解析、API请求或测试地理相关内容。
- 请求匿名化——在从容器访问外部服务时隐藏服务器的真实IP。
- 速率限制——Docker Hub对匿名用户限制拉取请求的数量(每6小时100次拉取)。通过IP轮换的代理可以绕过此限制。
重要的是要理解,Docker不是一个应用程序,而是由多个组件组成的系统。守护进程(dockerd)在主机上运行并进行镜像拉取。容器是具有自己网络设置的隔离进程。因此,为守护进程和容器配置代理是两个不同的任务,解决方法也不同。
Docker中的三个代理级别
在深入配置之前,需要了解架构。在Docker中有三个独立的级别,每个级别都需要单独配置代理:
| 级别 | 功能 | 配置位置 |
|---|---|---|
| Docker守护进程 | 下载镜像(docker pull),访问注册表 | systemd覆盖或daemon.json |
| Docker构建 | 在构建镜像时执行命令(RUN apt-get、pip install等) | Dockerfile中的ARG和ENV或--build-arg |
| 运行时容器 | 正在运行的应用程序,进行HTTP请求 | docker run时的ENV或docker-compose.yml中的ENV |
常见错误是仅在一个级别上配置代理,然后惊讶于为什么镜像仍然无法拉取或应用程序无法看到代理。让我们详细讨论每个级别。
为Docker守护进程配置代理(拉取镜像)
如果您希望通过代理拉取镜像,这是最重要的设置。Docker守护进程是一个通过systemd启动的系统服务。您的用户或甚至root的环境变量是不可见的。需要将代理传递给服务的环境中。
方法1:通过systemd覆盖(推荐)
创建一个目录以覆盖服务设置:
sudo mkdir -p /etc/systemd/system/docker.service.d
创建文件/etc/systemd/system/docker.service.d/http-proxy.conf,内容如下:
[Service] Environment="HTTP_PROXY=http://username:password@proxy-host:port" Environment="HTTPS_PROXY=http://username:password@proxy-host:port" Environment="NO_PROXY=localhost,127.0.0.1,::1,your-private-registry.example.com"
如果代理没有身份验证,只需删除username:password@。创建文件后,重新加载systemd配置并重启Docker:
sudo systemctl daemon-reload sudo systemctl restart docker
检查设置是否已应用:
sudo systemctl show --property=Environment docker
输出中应出现您的变量HTTP_PROXY和HTTPS_PROXY。
方法2:通过~/.docker/config.json(Docker Desktop和新版本)
从Docker Engine 23.0开始,出现了一种更方便的方法——通过客户端配置文件设置代理。创建或编辑文件~/.docker/config.json:
{
"proxies": {
"default": {
"httpProxy": "http://username:password@proxy-host:port",
"httpsProxy": "http://username:password@proxy-host:port",
"noProxy": "localhost,127.0.0.1,::1"
}
}
}
这种方法的优点是无需root权限和重启守护进程。设置会在构建时自动传递给容器作为构建参数。然而,对于管理守护进程的拉取请求,仍然需要通过systemd的方法。
💡 关于NO_PROXY的重要提示
始终将您的内部注册表和服务的地址添加到NO_PROXY中。否则,Docker将尝试通过代理访问它们并出现连接错误。典型列表:localhost,127.0.0.1,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
构建时容器内的代理
当Docker构建镜像并执行类似RUN apt-get install、RUN pip install或RUN npm install的命令时,这些命令在临时容器内执行。默认情况下,它无法访问主机的代理。需要通过构建参数显式传递代理。
通过--build-arg传递代理
docker build \ --build-arg HTTP_PROXY=http://proxy-host:port \ --build-arg HTTPS_PROXY=http://proxy-host:port \ --build-arg NO_PROXY=localhost,127.0.0.1 \ -t my-image .
在Dockerfile中配置
如果代理仅在构建阶段需要,并且您不希望它出现在最终镜像中——请使用ARG而不是ENV:
FROM ubuntu:22.04 # 声明构建参数 ARG HTTP_PROXY ARG HTTPS_PROXY ARG NO_PROXY # 在命令中使用它们 RUN apt-get update && apt-get install -y curl wget # 在此块之后,容器的ENV中将没有代理
如果代理在运行的容器中也需要——请使用ENV:
FROM python:3.11 ENV HTTP_PROXY=http://proxy-host:port ENV HTTPS_PROXY=http://proxy-host:port ENV NO_PROXY=localhost,127.0.0.1 RUN pip install requests beautifulsoup4 CMD ["python", "app.py"]
⚠️ 注意:安全性
如果镜像是公开的或将被放入仓库,请不要将代理的用户名和密码硬编码到Dockerfile中。使用--build-arg并从CI/CD系统的环境变量中传递值。命令docker history可以显示ARG的值,因此对于秘密请使用Docker BuildKit secrets。
运行时容器的代理
这是应用程序在运行时进行HTTP请求的最常见场景——解析器、机器人、需要通过特定IP访问的微服务。在这里,设置非常简单:需要在启动容器时传递环境变量。
通过docker run
docker run \ -e HTTP_PROXY=http://username:password@proxy-host:port \ -e HTTPS_PROXY=http://username:password@proxy-host:port \ -e NO_PROXY=localhost,127.0.0.1 \ my-image
大多数流行的HTTP库会自动获取HTTP_PROXY和HTTPS_PROXY变量:Python requests、curl、wget、Go的net/http、Node.js https-proxy-agent等。代码中无需额外的设置。
通过.env文件
更方便的做法是将设置存储在.env文件中,并整体传递:
# .env文件 HTTP_PROXY=http://username:password@proxy-host:port HTTPS_PROXY=http://username:password@proxy-host:port NO_PROXY=localhost,127.0.0.1
docker run --env-file .env my-image
容器中的SOCKS5代理
如果您使用SOCKS5代理(例如,住宅代理通常支持此协议),语法会有所不同:
docker run \ -e ALL_PROXY=socks5://username:password@proxy-host:port \ my-image
请注意:并非所有库都支持通过环境变量使用SOCKS5而无需额外依赖。Python requests需要安装requests[socks],curl必须在支持libcurl-socks的情况下构建。
Docker Compose中的代理
在实际项目中,通常不会使用裸docker run。通常使用Docker Compose,在一个文件中描述多个服务。这里的代理设置是在每个服务的级别上进行的,或通过变量文件。
选项1:docker-compose.yml中的环境变量
version: '3.8'
services:
scraper:
image: my-scraper:latest
environment:
- HTTP_PROXY=http://username:password@proxy-host:port
- HTTPS_PROXY=http://username:password@proxy-host:port
- NO_PROXY=localhost,127.0.0.1
restart: unless-stopped
api:
image: my-api:latest
# 此服务没有代理——仅访问内部资源
ports:
- "8080:8080"
选项2:通过.env文件(推荐)
在docker-compose.yml目录中创建.env。Docker Compose会自动获取此文件:
# .env PROXY_HOST=proxy-host PROXY_PORT=8080 PROXY_USER=username PROXY_PASS=password
version: '3.8'
services:
scraper:
image: my-scraper:latest
environment:
- HTTP_PROXY=http://${PROXY_USER}:${PROXY_PASS}@${PROXY_HOST}:${PROXY_PORT}
- HTTPS_PROXY=http://${PROXY_USER}:${PROXY_PASS}@${PROXY_HOST}:${PROXY_PORT}
- NO_PROXY=localhost,127.0.0.1
在Compose中构建时的代理
如果在Compose中有build部分,并且需要在构建阶段传递代理:
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
args:
HTTP_PROXY: http://${PROXY_USER}:${PROXY_PASS}@${PROXY_HOST}:${PROXY_PORT}
HTTPS_PROXY: http://${PROXY_USER}:${PROXY_PASS}@${PROXY_HOST}:${PROXY_PORT}
environment:
- HTTP_PROXY=http://${PROXY_USER}:${PROXY_PASS}@${PROXY_HOST}:${PROXY_PORT}
- HTTPS_PROXY=http://${PROXY_USER}:${PROXY_PASS}@${PROXY_HOST}:${PROXY_PORT}
为Docker选择哪种类型的代理
选择代理类型取决于任务。对于Docker环境,有三种主要类型,每种类型都有其特定的用途:
| 代理类型 | 速度 | 匿名性 | Docker的最佳场景 |
|---|---|---|---|
| 数据中心 | ⚡ 高速 | 中等 | 拉取镜像、CI/CD管道、绕过Docker Hub的速率限制 |
| 住宅 | 🔄 中等 | 高 | 解析受保护的网站、API地理限制、测试 |
| 移动 | 🔄 中等 | 最高 | 与移动API(Instagram、TikTok)交互的应用程序 |
对于拉取镜像和CI/CD,最佳选择是数据中心代理——它们在下载大型镜像(几个GB)时提供最大速度,并为长时间构建提供稳定连接。
对于需要绕过网站保护并看起来像普通用户的解析器容器,最佳选择是住宅代理。它们具有真实家庭用户的IP,即使在高强度请求下也降低了被阻止的可能性。
对于与移动平台(如Instagram Graph API、TikTok API、移动版本服务)交互的应用程序,建议考虑移动代理。它们使用移动运营商的IP,并在反机器人系统中引起的怀疑最小。
协议:HTTP与SOCKS5
Docker守护进程仅支持HTTP/HTTPS代理——SOCKS5不适用于拉取镜像。如果您有SOCKS5代理并需要下载镜像,则必须使用本地转换器,如privoxy或microsocks,它将接受HTTP并通过SOCKS5代理。
对于运行时容器,情况更好:大多数HTTP库通过ALL_PROXY=socks5://...直接支持SOCKS5。
常见错误及其解决方法
让我们讨论在Docker中配置代理时最常见的问题:
错误1:代理在主机上配置,但Docker无法识别
症状:
来自守护进程的错误响应:获取“https://registry-1.docker.io/v2/”:拨打tcp:连接被拒绝
原因:Docker守护进程作为系统服务启动,并且不会继承当前用户的环境变量,即使您在终端中设置了export HTTPS_PROXY=...。
解决方案:通过systemd覆盖配置代理(上面部分的第1种方法)。确保执行systemctl daemon-reload && systemctl restart docker。
错误2:在构建时apt-get / pip / npm无法工作
症状:
错误:1 http://archive.ubuntu.com/ubuntu focal InRelease
无法连接到archive.ubuntu.com:80
原因:为守护进程配置的代理不会自动传播到Dockerfile中的RUN命令。这是两个不同的上下文。
解决方案:通过--build-arg传递代理,或使用~/.docker/config.json,其中包含proxies部分(Docker 23.0+,在构建时自动传递参数)。
错误3:通过代理无法访问内部服务
症状:
curl: (7) 无法连接到internal-service.local端口8080:连接被拒绝
原因:代理试图将请求代理到内部地址,而这些地址在外部不可用。
解决方案:将所有内部域和子网添加到NO_PROXY中。对于公司网络,典型列表是:localhost,127.0.0.1,::1,.internal,.local,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
错误4:代理身份验证无效——密码中的特殊字符
如果密码包含特殊字符(@、#、%),URL解析不正确。
解决方案:对密码进行URL编码。例如,p@ss#word → p%40ss%23word。在Python中可以快速获得编码版本:
python3 -c "from urllib.parse import quote; print(quote('p@ss#word', safe=''))"
# 输出:p%40ss%23word
错误5:代理工作,但SSL证书未通过验证
企业代理通常执行SSL检查(MITM)并替换证书。此时,Docker会返回错误:
x509:由未知机构签署的证书
解决方案:将企业根证书添加到主机的受信任证书中,并重启Docker。在Ubuntu/Debian上:
sudo cp corporate-ca.crt /usr/local/share/ca-certificates/ sudo update-ca-certificates sudo systemctl restart docker
错误6:Kubernetes / Docker Swarm——代理在所有节点上无效
在集群环境中,需要在每个节点上单独配置代理。对于Kubernetes,还需要在Pod的清单或通过ConfigMap中配置环境变量。可以通过Ansible或其他配置管理工具来自动化此过程。
结论
在Docker中配置代理不是一次性设置,而是三个独立的级别:用于拉取镜像的守护进程、用于构建的构建时和用于运行的容器。了解这一架构后,您将能够灵活管理通过代理和直接访问的流量。
正确配置的简要检查清单:
- ✅ 对于拉取镜像——配置带有HTTP_PROXY和HTTPS_PROXY变量的systemd覆盖
- ✅ 对于构建——通过
--build-arg或~/.docker/config.json传递代理 - ✅ 对于运行时——通过
-e或--env-file使用环境变量 - ✅ 始终为内部地址配置NO_PROXY
- ✅ 不要在Dockerfile中存储代理密码——使用build-args和.env文件
- ✅ 对于CI/CD管道,选择快速的数据中心代理
- ✅ 对于解析器容器——使用住宅或移动代理
如果您的容器应用程序向外部服务发送请求、解析数据或与具有地理限制的API交互,建议使用住宅代理——它们提供了服务的高度信任,并在高强度工作下具有最低的阻止风险。