多阶段构建需严格分离构建与运行阶段,仅复制必要二进制产物,禁用测试依赖进入运行镜像,并通过路径精简、文件清理和扫描验证确保无测试工具残留。
多阶段构建避免把测试工具带入生产环境,关键在于严格分离构建阶段与运行阶段,并确保最终镜像只包含运行时必需的二进制产物,不复制任何构建依赖、源码或测试相关工件。
明确各阶段职责边界
构建阶段(如 AS build)负责编译、运行测试、生成可执行包;运行阶段(如 FROM openjdk:17-jre-slim)仅接收构建产物,不继承前一阶段的文件系统或环境。
- 测试工具(JUnit、Mockito、AssertJ 等)必须仅存在于构建阶段的 classpath 中,不能出现在运行阶段的依赖树里
- Maven 的
<scope>test</scope>依赖默认不会打入 final jar,但需确认未被意外设为compile或通过provided引入 - 避免在运行阶段执行
COPY --from=build target/这类宽泛路径,应精确指定目标文件(如COPY --from=build target/app.jar /app.jar)
精简 COPY 操作,杜绝“连带复制”
常见错误是用 COPY --from=build . /app/ 或 COPY --from=build target/ /app/,这会把 surefire-reports/、classes/、test-classes/ 等测试产物一并拖入运行镜像。
- 只复制明确构建输出:jar、war、二进制可执行文件或静态资源目录
- Java 项目中,检查
target/下是否残留*-tests.jar或original-*.jar,可在构建阶段末尾加清理命令:&& rm -f target/*-tests.jar target/original-*.jar - 前端项目中,不要
COPY --from=build dist/ .后又顺手COPY --from=build node_modules/ .—— 运行时不需要 npm 或 webpack
验证最终镜像不含测试类和工具
构建完成后,快速验证是防止漏网的关键动作。可在 CI 流程末尾加入轻量检查:
- 运行
docker run --rm <image> sh -c "ls -R /app | grep -i 'junit\|mockito\|test\|surefire'",预期无输出 - 对 jar 包执行
jar -tvf app.jar | grep -i '\.test\|Test\.class\|junit',确认无测试类字节码 - 使用
trivy image --severity CRITICAL,HIGH <image>扫描,重点关注是否引入了测试框架相关的 CVE(如旧版 JUnit 4.x)
利用构建参数和配置隔离测试依赖
即使使用 Maven,也要防止 profile 或 properties 导致测试依赖泄漏:
- 构建阶段显式启用测试:
RUN mvn clean test package -DskipTests=false -Ptest,但运行阶段绝不激活含测试依赖的 profile - 禁止在
application.yml或启动参数中引用测试专用配置(如spring.profiles.active=test),运行阶段应强制指定prod或runtime - 若用 Gradle,确认
bootJar或shadowJartask 不包含testRuntimeClasspath

















