关键在于分离构建与运行阶段:第一阶段用golang:1.21等含工具链镜像编译,以as builder命名;第二阶段用alpine或distroless镜像,仅通过copy --from=builder精确复制二进制文件,不安装任何开发工具,最终镜像不含go、gcc、npm等。
关键在于分离构建阶段和运行阶段,只在构建阶段安装工具链,再通过 --from 显式复制产物,跳过所有中间依赖和源码。
明确划分构建与运行两个阶段
第一阶段用带完整工具链的镜像(如 golang:1.21、node:20),专注编译或打包;第二阶段换用极简镜像(如 alpine:latest 或 gcr.io/distroless/static),不装任何开发工具。
- 构建阶段以
AS builder命名,便于后续引用 - 运行阶段不执行
RUN apt install或npm install等操作 - 避免在运行阶段
COPY . .整个目录,只复制编译产出物
精准复制产物,不带多余文件
使用 COPY --from=builder 指令时,路径必须精确到目标文件或必要目录,不能模糊匹配或递归拷贝整个工作区。
- 正确写法:
COPY --from=builder /app/myapp . - 错误写法:
COPY --from=builder /app .(可能带入go.mod、node_modules等) - 可配合
RUN rm -rf /tmp/*清理临时文件,但更推荐从源头控制复制范围
禁用运行阶段的包管理与构建命令
最终镜像里不应存在 go、gcc、npm、python3-dev 等工具。验证方式很简单:
- 构建后运行
docker run --rm <image> which go</image>,应无输出 - 检查镜像内容:
docker run --rm <image> ls /usr/bin | grep -E "go|gcc|npm|yarn"</image>,结果应为空 - 优先选用 distroless 或
scratch镜像作为运行基础,它们默认不含 shell 和包管理器
利用 BuildKit 提升构建隔离性
启用 BuildKit 后,构建过程更严格区分阶段上下文,临时层不会意外泄露到最终镜像中。
- 构建时加环境变量:
DOCKER_BUILDKIT=1 docker build . - 或全局启用:
export DOCKER_BUILDKIT=1 - BuildKit 默认跳过未被
--from引用的阶段,进一步保障安全性











