国内服务器 Docker 构建提速:从 41 分钟到 4 分钟
首次构建等了 41 分钟没结束,Maven 中央仓只有 15 kB/s。本篇用内联镜像源、替换 apk 源、调分层顺序三招,把它压到 4 分钟。
一、症状:一个永远结束不了的构建
在国内服务器上第一次执行 docker build,等了 41 分钟,没看到结束:
[INFO] Downloading from central: https://repo.maven.apache.org/maven2/org/springframework/...
Progress (1): 128/512 kB
Progress (1): 256/512 kB
...
卡在下载依赖上,进度条一格一格挪。
实测了几个数字:
| 操作 | 耗时 |
|---|---|
mvn dependency:go-offline(Maven 中央仓库) |
1714 秒(28.5 分钟) |
apk add curl tzdata(Alpine 默认源) |
345 秒(5.75 分钟) |
| 下载速度 | 15–316 kB/s |
两个瓶颈,两个原因,都要修。
二、瓶颈一:Maven 中央仓库
国内直连 repo.maven.apache.org 就是这个速度——不是网络故障,是线路问题,不会自己变好。
修复:在 Dockerfile 里内联阿里云镜像配置
# ---------------------------- 阶段 1:构建 -----------------------------------
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /build
# 内联阿里云 Maven 镜像配置。
# 【关键】放在 COPY pom 之前,这样只要镜像地址不变,这一层就始终命中缓存。
RUN mkdir -p /root/.m2 && cat > /root/.m2/settings.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0">
<mirrors>
<mirror>
<id>aliyun-central</id>
<name>Aliyun Maven Central Mirror</name>
<url>https://maven.aliyun.com/repository/central</url>
<mirrorOf>central</mirrorOf>
</mirror>
<mirror>
<id>aliyun-public</id>
<name>Aliyun Maven Public</name>
<url>https://maven.aliyun.com/repository/public</url>
<mirrorOf>*</mirrorOf>
</mirror>
</mirrors>
</settings>
EOF
COPY pom.xml .
COPY api-cloud-common/pom.xml ./api-cloud-common/
COPY api-cloud-service-user/pom.xml ./api-cloud-service-user/
# ...
为什么放两个 mirror
| mirror id | mirrorOf |
作用 |
|---|---|---|
aliyun-central |
central |
代理中央仓库 |
aliyun-public |
* |
兜底:代理所有其他仓库(含第三方仓库、插件仓库) |
只配 central 的话,项目里如果声明了其他 repository(很多 Spring 相关依赖会走 spring-milestones 等),那些请求还是会直连国外。
mirrorOf的匹配按声明顺序生效,先匹配到的胜出。所以central要写在*前面。
关键是那一行的位置
RUN mkdir -p /root/.m2 && cat > /root/.m2/settings.xml << 'EOF' # ← 先写 settings
...
COPY pom.xml . # ← 再拷 pom
Docker 的层缓存是从上到下的:任何一层内容变了,它以及它之后的所有层都会重建。
这两种写法的差别:
| 写法 | 改了 pom.xml 之后 |
|---|---|
settings 在 COPY pom.xml 之后 |
settings 层要重建 → 即使内容没变,也走一遍慢速下载 |
settings 在 COPY pom.xml 之前 |
settings 层命中缓存 → 直接复用,不重新下载 |
把这个顺序写对,是最省事的一次性优化。
三、瓶颈二:Alpine 的包管理源
第二阶段的基础镜像是 eclipse-temurin:17-jre-alpine,需要装 curl(healthcheck 依赖它,详见 C4 篇)和 tzdata:
# ❌ 默认写法:直连 dl-cdn.alpinelinux.org
RUN apk add --no-cache curl tzdata
dl-cdn.alpinelinux.org 在国内同样很慢——345 秒装两个小包。
修复:先换源再装
RUN sed -i 's|dl-cdn.alpinelinux.org|mirrors.aliyun.com|g' /etc/apk/repositories \
&& apk add --no-cache curl tzdata
换源后:3–10 秒(本项目实测整体降到约 170 秒,包含镜像层重建的开销)。
用
sed替换而不是覆盖整个/etc/apk/repositories文件,是因为不同 Alpine 版本的源地址格式略有差异,替换域名(而非重写文件)兼容性更好。顺带一提:
mirrors.aliyun.com的 Alpine 源路径结构与官方一致,所以只换域名就够。
四、修复效果
| 环节 | 修复前 | 修复后 |
|---|---|---|
| Maven 依赖下载 | 1714 秒 | 走 aliyun-central |
apk add curl tzdata |
345 秒 | ~3–10 秒 |
| common 模块构建 | —— | 33 秒 |
| 整体构建 | > 41 分钟未完成 | 约 4 分钟 |
验证是否真的走了镜像,看构建日志里有没有这个关键字:
Downloaded from aliyun-central: https://maven.aliyun.com/repository/central/...
看到 aliyun-central 才算生效。 如果日志里还是 repo.maven.apache.org,说明 settings.xml 没被 Maven 读到(路径写错、或 COPY 顺序错导致旧层被复用)。
五、顺带说一下分层设计
完整的 Dockerfile 分层顺序(本项目实际使用):
# ① 换源配置(几乎不变)
RUN mkdir -p /root/.m2 && cat > /root/.m2/settings.xml << 'EOF'
...
EOF
# ② 只拷 pom,不拷源码(pom 变化频率:低)
COPY pom.xml .
COPY api-cloud-common/pom.xml ./api-cloud-common/
COPY api-cloud-service-user/pom.xml ./api-cloud-service-user/
COPY api-cloud-service-business/pom.xml ./api-cloud-service-business/
COPY api-cloud-service-order/pom.xml ./api-cloud-service-order/
COPY api-cloud-gateway/pom.xml ./api-cloud-gateway/
# ③ 预拉依赖(最耗时,但只在 pom 变化时重建)
RUN mvn -B -q dependency:go-offline -DskipTests || true
# ④ 拷源码(变化频率:高)
COPY api-cloud-common/src ./api-cloud-common/src
COPY api-cloud-service-user/src ./api-cloud-service-user/src
# ⑤ 构建(只在源码变化时重建)
RUN mvn -B clean package -DskipTests -pl api-cloud-service-user -am \
&& cp api-cloud-service-user/target/*.jar /build/app.jar
核心思想:按"变化频率从低到高"排列。 日常改 Java 代码时,只有第 ④⑤ 层重建,依赖层的缓存一直有效。
第 ③ 步的
|| true是有意加的:dependency:go-offline有时会因为某些插件的元数据问题返回非 0,但它已经把该下的依赖都下完了。不能因为这步返回非 0 就让整个构建失败——后面真正的package会验证依赖完整性。
六、复盘
两条可以立刻用到自己项目里的优化:
- 任何在国内服务器上跑 Docker 构建的项目,第一件事是配 Maven / npm / pip 的国内镜像源,并且放在
COPY依赖清单之前,让它吃满缓存。 - Alpine 基础镜像装包前先
sed换源。dl-cdn.alpinelinux.org在国内是 5 分钟量级,换阿里云后是秒级。
一条更通用的经验:
构建慢不是"环境就是这样",而是有具体原因和具体量级的。先测量,再优化。
本项目之所以能定位到"两个独立瓶颈",是因为分别测了
dependency:go-offline和apk add的耗时,而不是笼统地觉得"docker build 很慢"。如果只优化其中一个,构建时间也只能减半。
原创文章,作者:ECHO陈文,如若转载,请注明出处:https://www.luweipai.cn/ops/1789977406/