多阶段构建能从根本上防止源码进入最终镜像,关键不是“删代码”,而是让源码只存在于中间阶段、根本不被复制过去;必须明确划分构建与运行阶段,源码仅限命名构建阶段(如as builder),运行阶段使用独立基础镜像并精确复制产物,禁用通配符复制,实测验证无.py文件残留,再配合distroless等极简镜像彻底压缩攻击面。

多阶段构建能从根本上防止源码进入最终镜像,关键不是“删代码”,而是让源码只存在于中间阶段、根本不被复制过去。
明确划分构建与运行阶段
源码必须只出现在命名的构建阶段(如 AS builder),且该阶段不能作为最终镜像的基底。运行阶段应使用独立基础镜像,不继承构建阶段的任何文件系统内容。
- 写法示例:用
FROM python:3.9-slim AS builder启动构建,而非直接FROM python:3.9-slim - 所有
COPY . .和源码相关操作(如RUN pip install -e .)都限定在builder阶段内 - 运行阶段从头开始,例如
FROM gcr.io/distroless/python3或FROM alpine:latest
只复制产物,不复制源目录
最终镜像只需要可执行工件,不需要 .py 文件、tests/、setup.py 或任何开发痕迹。复制动作必须精确到文件或打包后产物路径。
- 禁止:
COPY --from=builder /app/src/ .—— 这会把全部源码带入 - 推荐:
COPY --from=builder /app/dist/app.pyz /app/或COPY --from=builder /app/target/app.jar /app/ - 对 Python,可提前用
pip install --target ./dist --no-deps -r requirements-runtime.txt提取纯运行时依赖,再仅复制dist/
验证源码是否真正消失
不能只靠 Dockerfile 逻辑判断,必须实测确认源码未残留。
- 构建完成后运行:
docker run --rm -it your-image ls -R /app | grep "\.py$",应无输出 - 检查是否有常见源码路径:
docker run --rm your-image find / -name "*.py" 2>/dev/null | head -5 - 若发现
__pycache__、tests/、src/或setup.py,说明复制策略有误
配合极简基础镜像收尾
即使源码已剥离,若运行镜像仍含 shell、包管理器或调试工具,仍可能被用于反向工程或动态分析。需进一步压缩攻击面。
- Python 应用优先选
gcr.io/distroless/python3,它没有/bin/sh、pip或python3-config - Java 应用用
gcr.io/distroless/java17,不含jconsole、jstack、javac - Go 应用静态编译后直入
scratch,连 libc 都不存在,彻底杜绝解释或调试可能











