镜像拉取太慢?先搞清慢在哪

docker pull 半天不动,是网络问题还是镜像太大?小够教你定位慢的原因:看清镜像体积、选对 tag、理解分层下载和构建缓存,以及截至撰写时可行的加速思路。

我是小够,只推够用的 VPS。

docker pull 转半天没进度,第一反应是"网不好"。但小够处理过不少这种 case,慢的原因一半是镜像本身太大,先定位再动手。

第一步:看镜像有多大

docker images

mysql:8.0 四五百 MB,wordpress:6-php8.2-apache 也是这个量级。VPS 带宽如果是几十 Mbps,拉个 500MB 镜像要几分钟,正常,别慌。先建立正确的预期:500MB 的镜像在 50Mbps 带宽下理论最快也要 80 秒,加上解压,两三分钟是正常的。

再看你用的 tag:凡是带 -alpine 后缀的都是轻量版,比如 nginx:alpine 只有几十 MB,而 nginx 默认版几百 MB。能用 alpine 版就用 alpine 版,体积差一个数量级,拉取快、攻击面也小。选 tag 的原则:

  1. 优先 alpine/slim 变体,能小就小。
  2. 大版本号写全(如 mysql:8.0 别用 mysql),避免意外大版本升级,也方便回滚。
  3. 生产环境把 tag 钉死,latest 是给玩具项目用的。

第二步:看慢在哪个阶段

docker pull 的输出分阶段:

838d37a5d6e7: Pulling fs layer     # 正在下载这一层
838d37a5d6e7: Download complete   # 这层下完了
838d37a5d6e7: Extracting          # 正在解压
838d37a5d6e7: Pull complete       # 这一层搞定

卡在下载是网络问题,卡在解压是磁盘/CPU 慢——小机器解压大镜像确实费劲,耐心等。还有个常见状态 Waiting:这一层在等前面的层下完,下载是串行的,急不来。

理解分层还有个好处:第二次拉同一镜像家族(比如都是 node:20-alpine 底子),已经下载的层会复用,显示 Already exists,快很多。所以统一基础镜像是个好习惯:全家都用 alpine 底子,总体积小很多。

第三步:并发下载数

Docker 默认同时下载 3 层,层数多的镜像可以调大:

// /etc/docker/daemon.json
{
  "max-concurrent-downloads": 6
}

改完重启 Docker。小带宽机器别调太大,并发高了反而互相抢带宽,3-6 之间试。

加速思路(截至撰写时)

实话实说:截至 2025 年 7 月,国内公开的 Docker Hub 镜像加速站大多已经停服,指望"配个地址就飞起"的时代过去了。还靠谱的思路:

  1. 自建 registry 做 pull-through cache:在能直连的机器上搭个 registry 容器,一劳永逸,适合手里机器多的人。配一次,以后所有机器都从它拉,内网速度。
  2. 用云厂商的容器镜像服务:按量付费,把常用镜像同步过去再拉,稳定但要花小钱。量小的话一个月几块钱,比折腾时间便宜。
  3. 换思路降需求:alpine 小镜像 + 按需拉取,体积小了,对网络的要求自然就低了。一个 50MB 的镜像,再慢的网络也就一会儿。

小够不推荐满世界找"最新可用加速地址"——今天能用明天关,时间全浪费在折腾上,不值。有那时间不如把镜像体积打下来,这是治本。

顺手的三个习惯

# 1. 拉取时只拿需要的平台(ARM 机器别拉 AMD64 的,体积差不少)
docker pull --platform linux/arm64 nginx:alpine

# 2. 定期清掉没用的镜像,省磁盘
docker image prune -a

# 3. 看看镜像里到底装了啥(dive 工具,学优化时用)
docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock wagoodman/dive:latest nginx:alpine

prune -a 会删所有没被容器用的镜像,执行前确认没有"以后还想用"的,删了就得重拉。dive 能可视化每一层的内容和体积,学 Dockerfile 优化时很好用,一眼看出哪一层是胖子。

构建慢也是一回事

docker build 慢,原因和拉取类似:基础镜像大、RUN 里下载慢。解法:换小基础镜像、合并 RUN 减少层数、利用构建缓存(把不常变的东西放前面,Dockerfile 那篇讲过)。构建时加 --progress=plain 看详细过程,卡在哪一步一目了然:

docker build --progress=plain -t myapp:v1 .