多阶段构建通过使用distroless或scratch镜像剔除shell、包管理器等非必需组件,隔离源码与密钥,精简运行时依赖,并强制非root用户运行,显著收窄攻击面。

多阶段构建能直接剔除运行环境中所有非必需的可执行程序和工具,让攻击者失去立足点。最终镜像里没有 shell、没有包管理器、没有编译器,连基础调试能力都不存在,攻击面自然大幅收窄。
移除默认 Shell 和交互式工具
传统镜像(如 ubuntu:22.04 或 node:18)默认包含 /bin/sh、/bin/bash、curl、wget、apk、apt 等,这些都可能被用于横向移动或命令注入。多阶段构建时,选用 distroless 或 scratch 作为最终基础镜像,就能彻底剥离它们。
-
distroless 镜像:只含运行时依赖(如 JRE 或 glibc),无 shell,无包管理器,官方由 Google 维护(如
gcr.io/distroless/java17) -
scratch 镜像:完全空白,仅能运行静态链接二进制(如 Go 应用设
CGO_ENABLED=0后可直接用) - 避免使用
alpine中的sh—— 虽小但仍是完整 shell,仍支持命令执行和文件遍历
杜绝源码与构建密钥残留
单阶段构建中,COPY . . 常把 .git、secrets.json、Makefile、测试用例甚至 IDE 配置一并打包进去。多阶段构建天然隔离:源码只存在于 builder 阶段,且该阶段不会被推送到仓库或部署到生产环境。
- builder 阶段可自由安装 git、ssh-agent、私有 registry 凭据等,只要不 COPY 到 final 阶段,就完全不出现在生产镜像中
- 敏感构建参数(如
--build-arg API_KEY)在构建完成后即销毁,不会写入任何镜像层 - 推荐显式声明要复制的文件路径,例如
COPY --from=builder /app/dist/app.js /usr/share/nginx/html/,而非模糊的COPY --from=builder /app/ .
精简运行时依赖链
攻击面不仅来自“有啥”,更来自“依赖啥”。Java 应用若用 openjdk:17,会带完整 JDK(含 jconsole、jstack、javac);Node.js 若用 node:18,则含 npm、npx、python3 等构建依赖。多阶段构建允许你只取最小运行子集。
- Java:改用
eclipse-temurin:17-jre-alpine或gcr.io/distroless/java17,体积从 500MB+ 降至 150MB 以内,同时移除全部开发工具 - Node.js:用
node:18-alpine构建,再 COPYdist/到nginx:alpine或nginx:stable-perl,彻底去掉 Node 运行时 - Go:静态编译后直接进
scratch,连 libc 都不需要,漏洞面趋近于零
消除冗余用户与权限配置
很多单阶段镜像以 root 用户启动,且未降权。多阶段构建配合 USER 指令可实现更细粒度控制:
- final 阶段显式创建非 root 用户(如
adduser -D -u 1001 appuser),再USER appuser - distroless 镜像默认不带用户系统,必须手动创建,反而倒逼权限最小化设计
- 避免在 final 阶段使用
RUN apk add或apt-get install,这类操作本身就会引入新软件包和潜在漏洞


















