Docker 多阶段构建:把 Go 后端镜像从 900MB 瘦到 20MB
Docker 多阶段构建:把 Go 后端镜像从 900MB 瘦到 20MB
先说结论
Go 程序编译出来就是一个静态二进制,理论上镜像可以很小,但很多人写的 Dockerfile 一上来就 FROM golang,把编译器、源码、依赖缓存全打进最终镜像,900MB 起步。换成多阶段构建:第一阶段用 Go 官方镜像编译,第二阶段只把编译好的二进制拷进一个最小基础镜像,镜像直接瘦到 20MB 左右,推送快、拉取快、攻击面也小。
第一步:一个能跑的多阶段 Dockerfile
假设项目在模块根目录,有 go.mod,入口是 main.go:
# 第一阶段:构建
FROM golang:1.23-alpine AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /out/server .
# 第二阶段:运行
FROM alpine:3.20
WORKDIR /app
COPY --from=builder /out/server ./server
EXPOSE 8080
ENTRYPOINT ["./server"]
关键点有三个。CGO_ENABLED=0 让 Go 生成纯静态二进制,不依赖系统 C 库,这样第二阶段用 Alpine 甚至 scratch 都能跑;go mod download 单独一层,利用 Docker 层缓存——只要 go.mod 没变,依赖就不用重新下载;COPY --from=builder 只把产物拷过去,编译器和源码都留在第一阶段,不会进最终镜像。
第二步:验证瘦身效果
docker build -t order-backend:v1 .
docker images order-backend
对比一下就知道差距:单阶段 FROM golang 的镜像通常 800MB 以上,多阶段 + Alpine 一般在 20MB 上下;如果第二阶段换成 FROM scratch,连 Alpine 的几 MB 也省了,只剩二进制本身的大小。不过 scratch 里什么都没有,连 shell 和 CA 证书都没有,服务要调外部 HTTPS 接口时会报证书错误,需要多拷一个 ca-certificates.crt,还要接受出问题时进不去容器排查的代价。对只有自己和女友用的点餐后端,Alpine 是更稳妥的选择:有 shell 能 docker exec 进去看一眼,体积也足够小。
第三步:上线部署时的两个习惯
第一,给镜像打上有意义的 tag 再推:order-backend:20261109 比 latest 好,回滚时知道到底回滚到哪个版本;latest 永远指向最后一次构建,出问题没法一眼定位。第二,构建时顺手加 --trimpath -ldflags="-s -w":
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /out/server .
-s -w 去掉符号表和调试信息,二进制能再小 20% 到 30%,对生产部署完全够用;本地调试再用默认参数编译,需要堆栈信息时不受影响。
检查清单
- 最终镜像里只有二进制,没有 Go 编译器和源码:
docker images看大小,docker history看各层。 - 二进制是静态编译的:
CGO_ENABLED=0写了没有。 - 依赖下载走了缓存层:改业务代码后重新构建,
go mod download那层是不是Using cache。 - 容器里能正常调外部接口:Alpine 阶段带了 CA 证书,HTTPS 请求不报错。
- 镜像 tag 带日期或版本号,不用裸
latest上生产。
多阶段构建的思路其实很通用:构建环境和运行环境分开,最终产物只带运行必需的东西。Go 天生适合这一套,镜像瘦下来之后,每次部署推送拉取都只要几秒,小服务器的磁盘和带宽也都省下来了。
- 点赞
- 收藏
- 关注作者
评论(0)