多阶段构建本身不能自动防止代码泄露,真正起效的是严格划分构建与运行阶段:源码仅限命名构建阶段(如as builder),运行阶段须从零开始使用distroless等极简镜像,精确复制产物(如app.pyz),禁用通配符拷贝,并实测验证无.py文件残留。
多阶段构建本身不能“自动”防止代码泄露,它只提供机制;真正起作用的是你如何使用这个机制——核心是让源码永远不进入最终镜像,而不是靠事后删除。
明确划分构建与运行两个独立阶段
源码必须只出现在命名的构建阶段(比如 AS builder),且该阶段不能作为最终镜像的基础。运行阶段要从零开始,用 distroless、alpine 或 scratch 这类极简镜像,不继承任何构建阶段的文件系统。
- 正确写法:
FROM python:3.11-slim AS builder→ 拷贝源码、装依赖、打包;再写FROM gcr.io/distroless/python3作为运行阶段 - 错误写法:直接
FROM python:3.11-slim,然后在同一个阶段里又 COPY 又 RUN 又删源码——删不干净,层还在
只复制产物,禁用通配符和目录级拷贝
最终镜像只需要可执行工件(如 app.pyz、app.jar、myapp),不需要 .py、src/、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 find / -name "*.py" 2>/dev/null | head -5,应无输出 - 检查常见残留路径:
docker run --rm your-image ls -R /app | grep -E "(src|tests|__pycache__|\.py$)" - 如果发现
setup.py或pyproject.toml,说明 COPY 策略没控制好
用 distroless 或 scratch 收尾,压缩攻击面
即使源码已剥离,若运行镜像还带 /bin/sh、pip、python3-config,攻击者仍可能反向工程或动态分析。
- Python 应用优先选
gcr.io/distroless/python3(无 shell、无包管理器) - Java 应用用
gcr.io/distroless/java17(不含jstack、jconsole) - Go 应用静态编译后直入
scratch(连 libc 都没有,彻底杜绝解释可能)











