在 VPS 上执行 docker pull,卡在 Pulling fs layer,最后报 i/o timeout、TLS handshake timeout,或直接出现 429 Too Many Requests、x509: certificate signed by unknown authority?这几类错误原因不同。先别把别人分享的镜像加速地址贴进 /etc/docker/daemon.json:改错配置可能让 Docker 启动失败,还可能把镜像请求交给不可信的第三方。
这篇按 Ubuntu VPS + Docker Engine 写,先定位是宿主机网络、daemon 代理、Docker Hub 限流、私有仓库证书,还是磁盘问题,再改对应的一层。已能拉取镜像、但容器启动后解析不了域名,请转到站内的 Docker 容器无法联网排查;那是另一个故障点。
在 VPS 上记录 Docker 版本、当前 context、daemon 状态和一次失败输出。以下命令不会修改 Docker 配置:
docker version
docker context ls
sudo systemctl status docker --no-pager
docker pull hello-world:latest
sudo journalctl -u docker -n 100 --no-pager
如果 docker version 显示连不上 daemon,先解决服务或权限问题;docker context ls 显示当前连接的是别的主机时,应到实际运行 daemon 的机器上排查网络。不要用本地笔记本的 curl 成功来证明 VPS 的 dockerd 能访问 Docker Hub。
| 看到的报错 | 更可能是哪一层 | 下一步 |
|---|---|---|
lookup registry-1.docker.io: no such host | VPS 宿主机 DNS | 查 getent、resolvectl、云厂商 DNS |
dial tcp ... i/o timeout、TLS handshake timeout | VPS 出站 443、代理或链路 | 对比 curl -4/-6、daemon 代理设置 |
You have reached your pull rate limit | Docker Hub 拉取配额 | 登录账号、减少重复拉取或等待窗口恢复 |
只有 429 Too Many Requests | Docker Hub 反滥用限流,也可能是中间代理 | 看完整响应和 daemon 日志,不要先改 DNS |
x509: certificate signed by unknown authority | CA 信任链或企业代理 | 核对目标仓库证书和可信 CA |
manifest unknown、pull access denied | 镜像名、tag 或仓库权限 | 校对镜像路径和登录身份 |
no space left on device | 镜像存储空间或 inode | 查 Docker 数据目录与磁盘,不要反复重拉 |
在 VPS 的宿主机执行:
getent ahosts registry-1.docker.io
getent ahosts auth.docker.io
curl -4 -sS -o /dev/null -w 'IPv4 HTTP %{http_code}, connect %{time_connect}s, TLS %{time_appconnect}s\n' --connect-timeout 10 https://registry-1.docker.io/v2/
curl -6 -sS -o /dev/null -w 'IPv6 HTTP %{http_code}, connect %{time_connect}s, TLS %{time_appconnect}s\n' --connect-timeout 10 https://registry-1.docker.io/v2/
/v2/ 在未带认证 token时返回 401 是正常的:它说明域名解析、TCP 和 TLS 至少走通了,不表示镜像有权限拉取。curl -4 成功而 curl -6 超时,提示 IPv6 链路可能有问题,但仍要对照 Docker 日志和云厂商网络设置,不要直接全局禁用 IPv6。两种连接都失败时,先看 VPS 的出站防火墙、安全组、代理、DNS 和所在网络对 Docker Hub 的可达性。
curl 用的是你当前 shell 的代理环境;真正拉镜像的是 Docker daemon。即使上面的测试成功,daemon 没配置代理也可能继续超时。反过来,daemon 已走代理而你的 shell 没有,也可能只见到 curl 失败。因此把这组命令当作分层诊断,不要当作最终结论。
如果这台 VPS 的外网访问必须经过你信任的 HTTP 代理,按 Docker 官方 daemon 代理文档修改 /etc/docker/daemon.json。先备份现有文件;如果文件已经有 log-driver、registry-mirrors 等键,只合并 proxies 对象,不要覆盖整个文件:
sudo install -d -m 755 /etc/docker
if [ -f /etc/docker/daemon.json ]; then sudo cp -a /etc/docker/daemon.json "/etc/docker/daemon.json.bak.$(date +%F-%H%M%S)"; fi
sudo nano /etc/docker/daemon.json
例如,已有代理在 proxy.example.com:3128,可在 JSON 中加入:
{
"proxies": {
"http-proxy": "http://proxy.example.com:3128",
"https-proxy": "http://proxy.example.com:3128",
"no-proxy": "localhost,127.0.0.1,.internal.example.com"
}
}
示例域名不能直接使用,要换成你实际可达且可信的代理。https-proxy 的值以 http:// 开头并不矛盾:常见 HTTP 代理用 CONNECT 转发 HTTPS。把私有仓库加入 no-proxy 前先确认它是否真的可直接访问。代理 URL 中若含账号密码,要限制配置文件读取权限,避免在截图、日志或仓库泄露。
改动前先做语法与 daemon 配置验证;计划一次短暂维护窗口再重启 Docker,因为重启 daemon 可能影响运行中的业务:
sudo python3 -m json.tool /etc/docker/daemon.json >/dev/null
sudo dockerd --validate --config-file=/etc/docker/daemon.json
sudo systemctl restart docker
sudo systemctl status docker --no-pager
docker pull hello-world:latest
如果 dockerd --validate 不通过,先恢复备份,不要重启。Docker 也支持通过 systemd 环境变量配置 daemon 代理,但若 daemon.json 同时设置了相同项,以 daemon 配置为准。~/.docker/config.json 里的代理主要传给新建容器和构建任务,不能代替 dockerd 的拉取代理;这是很多“终端能上网、pull 仍超时”的根源。Docker CLI 代理文档明确区分了这两层。使用 Docker Desktop 或 rootless Docker 时,配置位置与服务管理命令不同,不照搬这组 Ubuntu systemd 命令。
Docker Hub 官方配额页当前列出的六小时拉取上限是:未登录用户按 IPv4 地址或 IPv6 /64 网段 100 次,登录的 Personal 用户 200 次;付费订阅的拉取规则不同,数字应以官方实时页面为准。共享出口 IP 的 VPS 更容易把匿名配额用完。完整错误若写着 You have reached your pull rate limit,先在 VPS 上用 docker login 登录自己的账号,再确认部署脚本没有每分钟反复拉取同一镜像。
如果只看到简短的 429 Too Many Requests,还可能是 Docker Hub 的反滥用限流,与上面的六小时拉取配额不是一回事。先停止循环重试,查看 Docker Hub 排障说明和 daemon 日志,再决定等待、减少并发或联系服务方。换一个未核实的公共镜像站,不会把权限或限流问题自动变成可靠部署。
如果你有自建或可信的 Docker Hub pull-through cache,可以按 Docker 官方 mirror 文档在 daemon.json 合并 registry-mirrors。这只针对 Docker Hub 镜像路径;ghcr.io 等其他仓库仍应分别检查自己的网络与认证。切勿用来源不明、无维护承诺的公共 mirror 代替官方 registry,更不要为了“能拉下来”关闭 TLS 验证。
如果报 x509,先确认系统时间 timedatectl status,再确认错误指向 Docker Hub、企业代理还是你的私有 registry。私有仓库使用内部 CA 时,Docker 官方要求把可信 CA放在 /etc/docker/certs.d/<仓库域名:端口>/ca.crt(默认 443 不写端口),不是把服务端证书随意改名放进去。Linux Docker Engine 会把该目录中的 .crt 文件作为 CA 根证书;详见 Docker registry 证书文档。由代理替换证书的环境,还要让宿主机信任该代理的企业根 CA。不要将仓库改成 insecure-registries 来掩盖未知证书来源。
镜像层下载过程中出现 no space left on device,应先找 Docker 实际数据目录,而不是假定一定在 /var/lib/docker。Docker Engine 新版本可能把镜像内容放在 containerd 的存储目录;先执行:
docker info --format 'Docker Root Dir: {{.DockerRootDir}}'
df -h / /var/lib/docker /var/lib/containerd 2>/dev/null || true
df -i /
docker system df
确认是哪块磁盘、哪个目录占满后,再决定扩容或删除已确认不再使用的镜像。不要在生产 VPS 上直接运行 docker system prune -a --volumes:卷可能包含数据库或应用数据。网络恢复后,可重新 docker pull <镜像:固定版本> 验证;生产部署尽量固定版本或 digest,并在更新前保留回滚方案。
docker pull返回成功,docker image inspect <镜像:版本>能看到镜像。sudo journalctl -u docker -n 50 --no-pager没有持续出现相同的代理、TLS 或限流错误。docker info显示的是预期的 registry mirror、Docker Root Dir 和运行环境;临时修改已整理进正式配置。- 已运行的业务容器仍正常,计划中的
docker compose pull与更新流程可重复执行。
不要只凭一次 curl 返回 401 就宣布修好了:真正的验收是 目标镜像在目标 VPS 的 daemon 上成功拉取。如果拉取正常、容器却上不了网,再按站内的 Docker 容器 DNS 与 bridge 网络排查继续。
参考资料:Docker daemon 代理配置、Docker Hub 限流、registry mirror、registry 证书。
