安全生产镜像核心是运行阶段彻底剥离构建残留:禁用编译器、源码、包管理器、shell等;选用alpine:3.20、distroless/java17或scratch作基础镜像;精准copy构建产物;运行时切非root用户、设stopsignal、配jvm参数;本地验证路径干净与漏洞扫描。
多阶段构建写安全的生产镜像,核心就一条:运行阶段不带任何构建残留——没编译器、没源码、没包管理器、没 shell(可选)、没 .git 或 target 目录。安全不是靠加东西,而是靠减东西。
用最小化基础镜像启动运行阶段
别用 ubuntu:22.04 或 centos:7 作最终镜像,它们自带大量工具和用户权限机制,攻击面大。优先选:
-
alpine:3.20(轻量、社区维护活跃,含sh和apk,适合需简单调试的场景) -
distroless/java17(Google 官方 distroless 系列,无 shell、无包管理器,仅含 JRE 和证书,Java 应用首选) -
scratch(真正空镜像,只适合静态编译的 Go/Rust 二进制,需确保程序自带所有依赖,如 ca-certificates 要显式嵌入或挂载)
注意:alpine 默认用 musl libc,某些 Java native 库或 JNI 调用可能不兼容;distroless 不含 shell,docker exec -it 进不去,但正因如此才更安全。
构建阶段命名清晰,复制路径精准
命名阶段(AS builder)不只是为了可读,更是为了 COPY --from= 能准确引用,避免误拷整个构建环境。
- 只复制编译产出物,比如
COPY --from=builder /app/target/myapp.jar .,而不是COPY --from=builder /app/target/ . - 避免复制
.m2、node_modules、build/、src/、test/等目录 - Java 项目建议用
spring-boot-maven-plugin打成 fat jar,再 COPY 单个 jar;Go 项目用CGO_ENABLED=0 go build静态链接,避免运行时缺库
运行阶段主动剥离权限与能力
即使用了最小镜像,也要限制容器行为:
- 用
USER 1001:1001切换到非 root 用户(提前在镜像中addgroup -g 1001 -f appgroup && adduser -S appuser -u 1001) - 加
STOPSIGNAL SIGTERM,确保优雅退出 - Java 应用加上 JVM 参数:
-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0,防止 OOM 杀进程 - 禁止挂载敏感宿主机路径(靠 Kubernetes 或 Docker daemon 策略控制,Dockerfile 中不体现,但设计时要预判)
验证交付物是否真干净
构建完别急着推,本地快速验一验:
-
docker run --rm -it <your-image> ls -la /</your-image>—— 检查有没有/usr/src、/root/.m2、/app/src等不该存在的路径 -
docker run --rm -it <your-image> sh -c 'which gcc || which apk || which javac'</your-image>—— 应全部返回空 -
trivy image <your-image></your-image>扫描 CVE,重点关注high/critical漏洞是否来自基础镜像本身(alpine/distroless 通常极少) - 对 Java jar,用
jar -tvf myapp.jar | head -20确认没打进测试类或配置明文










