多阶段构建可将Go应用镜像从900MB+压缩至约20MB,核心是构建阶段用golang镜像编译,运行阶段仅COPY静态二进制到scratch或alpine镜像,剔除SDK、缓存、源码等冗余内容。

直接用多阶段构建(Multi-stage Builds),配合 scratch 或 alpine 作为运行阶段基础镜像,能把典型 Golang 应用镜像从 900MB+ 压到 20MB 左右——不是靠删文件,而是从构建逻辑上剔除所有运行时不需要的东西。
为什么单阶段构建会让 Go 镜像暴涨到 1GB?
Go 编译出的是静态二进制,但很多人仍用 golang:1.21 直接构建并运行,结果把整套 SDK、/go/pkg/mod 缓存、源码全塞进镜像。用 docker history 一看,光 /usr/local/go 就占 800MB+,而你的 myapp 二进制可能才 12MB。
- 错误写法:
FROM golang:1.21→COPY . .→RUN go build→CMD ["./myapp"] - 问题不止是体积:源码、
go.mod、构建时的环境变量(比如私有仓库 token)都留在镜像里,存在安全风险 - 哪怕加了
CGO_ENABLED=0 GOOS=linux,也救不了基础镜像本身臃肿
多阶段构建必须写的两个关键点
核心就两条:构建阶段用完整工具链,运行阶段只 COPY 二进制。中间不能漏掉 AS builder 和 --from=builder 这两个标记,否则 Docker 不知道该从哪一阶段取文件。
- 构建阶段必须显式命名:用
FROM golang:1.21 AS builder,不能省略AS builder - 运行阶段 COPY 必须带来源阶段名:
COPY --from=builder /app/myapp .,写成--from=0虽然能用,但可读性差、重构易错 - 构建阶段里,
go mod download要单独 RUN 一层,这样依赖缓存能被复用;COPY . .放在它后面,避免每次改代码都重下模块
运行阶段选 scratch 还是 alpine?
scratch 是真正的空镜像(0B),适合纯静态 Go 二进制;alpine 有 sh 和 apk,方便调试或需要动态链接时用。别用 debian 或 ubuntu,它们默认就 70MB+,抵消了多阶段一半效果。
- 选
scratch的前提:确保二进制是静态编译的,即构建时加了CGO_ENABLED=0 - 如果应用要调用系统命令(比如
exec.Command("ls")),scratch会报no such file or directory错误,这时得换alpine - 想验证是否真静态:本地执行
ldd myapp,输出not a dynamic executable才算过关
容易被忽略的构建细节
很多团队写了多阶段却没压到理想体积,问题常出在构建阶段残留或 COPY 范围过大。比如忘记清理 /tmp、把 testdata/ 也 COPY 进去了,或者用 COPY . . 把 node_modules(如果有前端资源)一起拖进 builder 阶段。
- 构建阶段最后加一句
RUN rm -rf /tmp/*,有些 Go 工具会在那里留缓存 - 运行阶段不要
COPY --from=builder /app .,而要精确到文件:COPY --from=builder /app/myapp / - 如果项目含非 Go 文件(如 config.yaml、SQL 迁移脚本),在运行阶段单独
COPY,别混进 builder 阶段 - 启用 BuildKit(
DOCKER_BUILDKIT=1)后,RUN指令支持--mount=type=cache,能加速go mod download,但对体积无直接影响
真正卡住体积下限的,从来不是语法细节,而是“有没有意识到:运行容器只需要一个二进制+它能执行的最小操作系统”。其余全是干扰项。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











