news 2026/4/18 11:10:40

Docker镜像瘦身实战:从1G优化到50M的全过程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Docker镜像瘦身实战:从1G优化到50M的全过程

上次review同事的Dockerfile,一个Go服务的镜像打出来1.2G。

“这也太大了吧?”
“能跑就行呗。”

能跑是能跑,但每次部署拉镜像就要好几分钟,磁盘空间也吃不消。

花了半天时间优化,最后压到47M,记录一下过程。

问题分析

先看看原来的Dockerfile:

FROM golang:1.21 WORKDIR /app COPY . . RUN go mod download RUN go build -o main . EXPOSE 8080 CMD ["./main"]

看起来没毛病,但问题就出在这里。

镜像分析

# 查看镜像大小docker images|grepmyapp myapp latest abc1231.24GB# 用dive分析镜像层dive myapp:latest

分析结果:

  • golang:1.21基础镜像就有800MB
  • 加上源码、依赖、编译产物,妥妥过1G

优化方案

阶段一:多阶段构建

最立竿见影的优化:编译和运行分开。

# 阶段1:编译 FROM golang:1.21 AS builder WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -o main . # 阶段2:运行 FROM alpine:3.18 WORKDIR /app COPY --from=builder /app/main . EXPOSE 8080 CMD ["./main"]

效果:

docker images|grepmyapp myapp latest def456 28MB

直接从1.2G降到28MB,降了97%。

原理很简单:

  • 编译阶段用完整的golang镜像
  • 运行阶段只拷贝编译好的二进制文件
  • 用alpine替代完整系统,本身才5MB

阶段二:进一步压缩

28MB还能更小吗?可以。

# 阶段1:编译 FROM golang:1.21-alpine AS builder WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o main . # 阶段2:运行 FROM scratch COPY --from=builder /app/main /main EXPOSE 8080 ENTRYPOINT ["/main"]

改进点:

  • 编译阶段也用alpine,加快构建
  • 加上-ldflags="-s -w"去掉调试信息
  • scratch空镜像替代alpine
docker images|grepmyapp myapp latest ghi789 12MB

从28MB又降到12MB。

阶段三:UPX压缩(可选)

如果想更极致:

FROM golang:1.21-alpine AS builder # 安装upx RUN apk add --no-cache upx WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o main . RUN upx --best --lzma main FROM scratch COPY --from=builder /app/main /main EXPOSE 8080 ENTRYPOINT ["/main"]
docker images|grepmyapp myapp latest jkl0124.7MB

从12MB降到4.7MB。

但UPX有个问题:程序启动时需要解压,会增加启动时间。适合对镜像大小极度敏感但对启动速度不敏感的场景。

不同语言的优化策略

Java项目

Java比较麻烦,因为需要JVM。

# 阶段1:编译 FROM maven:3.9-eclipse-temurin-17 AS builder WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn package -DskipTests # 阶段2:运行 FROM eclipse-temurin:17-jre-alpine WORKDIR /app COPY --from=builder /app/target/*.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]

关键点:

  • 用jre-alpine替代完整JDK
  • 分离依赖下载和代码编译(利用缓存)

Java还可以用jlink自定义运行时:

FROM eclipse-temurin:17 AS jre-builder RUN jlink \ --add-modules java.base,java.logging,java.sql,java.naming,java.management \ --strip-debug \ --no-man-pages \ --no-header-files \ --compress=2 \ --output /javaruntime FROM alpine:3.18 COPY --from=jre-builder /javaruntime /opt/java COPY --from=builder /app/target/*.jar /app/app.jar ENV PATH="/opt/java/bin:${PATH}" ENTRYPOINT ["java", "-jar", "/app/app.jar"]

自定义的JRE只有几十MB,比完整JRE小很多。

Node.js项目

# 阶段1:构建 FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY . . RUN npm run build # 阶段2:运行 FROM node:20-alpine WORKDIR /app COPY --from=builder /app/dist ./dist COPY --from=builder /app/node_modules ./node_modules COPY package*.json ./ USER node EXPOSE 3000 CMD ["node", "dist/index.js"]

Node项目主要是node_modules太大,优化方向:

  • 只装生产依赖
  • npm ci替代npm install
  • 考虑用esbuild打包成单文件

Python项目

# 阶段1:构建 FROM python:3.11-alpine AS builder RUN apk add --no-cache gcc musl-dev WORKDIR /app COPY requirements.txt . RUN pip install --user --no-cache-dir -r requirements.txt # 阶段2:运行 FROM python:3.11-alpine WORKDIR /app COPY --from=builder /root/.local /root/.local COPY . . ENV PATH=/root/.local/bin:$PATH EXPOSE 8000 CMD ["python", "app.py"]

缓存优化

镜像大小优化完了,顺便说说构建速度。

利用层缓存

# 好的写法:先复制依赖文件 COPY go.mod go.sum ./ RUN go mod download # 再复制源码 COPY . . RUN go build -o main .
# 差的写法:一起复制 COPY . . RUN go mod download RUN go build -o main .

好的写法只要依赖不变,go mod download这层就会走缓存。

.dockerignore

别忘了加.dockerignore:

.git .gitignore *.md .idea .vscode node_modules vendor *.log Dockerfile docker-compose.yml

不然COPY .会把一堆没用的东西复制进去。

安全优化

镜像瘦身的同时,顺便做一下安全加固。

非root用户

FROM alpine:3.18 RUN addgroup -S appgroup && adduser -S appuser -G appgroup WORKDIR /app COPY --from=builder /app/main . RUN chown -R appuser:appgroup /app USER appuser EXPOSE 8080 CMD ["./main"]

只读文件系统

# docker-compose.ymlservices:app:image:myappread_only:truetmpfs:-/tmp

扫描漏洞

# 用trivy扫描trivy image myapp:latest

选择维护良好的基础镜像,及时更新。

实际效果对比

优化阶段镜像大小构建时间
原始版本1.24GB45s
多阶段+alpine28MB38s
scratch+ldflags12MB35s
UPX压缩4.7MB52s

推荐停在"scratch+ldflags"这个阶段,性价比最高。

部署效率提升

镜像从1.2G降到12MB后:

  • 推送到仓库:从3分钟变成5秒
  • 拉取镜像:从2分钟变成2秒
  • 磁盘占用:一台机器能放更多版本

特别是跨区域部署的时候,镜像小就是快。我们有几个异地节点,之前用星空组网把节点连起来后,小镜像部署基本感觉不到延迟。

常见问题

Q1:scratch镜像没有shell怎么调试?

# 需要调试就用alpine FROM alpine:3.18 # 或者用busybox FROM busybox:latest

Q2:CGO_ENABLED=0是什么意思?

禁用CGO,编译成纯静态二进制。不依赖glibc,才能在scratch里跑。

如果你的代码用了CGO(比如用了sqlite3),就不能这样玩。

Q3:alpine里程序跑不起来?

可能是glibc的问题。alpine用的是musl。

解决方案:

  • 编译时用alpine对应的golang镜像
  • 或者静态编译

总结

Docker镜像瘦身的核心技巧:

技巧适用场景效果
多阶段构建所有项目立竿见影
小基础镜像大多数项目很明显
ldflags去调试信息Go项目减少30-50%
UPX压缩对大小极端敏感减少60-70%
.dockerignore所有项目加快构建

一句话总结:多阶段构建 + 合适的基础镜像,就能解决90%的问题。


有其他镜像优化技巧欢迎评论区分享~

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 8:05:03

【大模型开发者必看】:Open-AutoGLM开源平台如何重构AI研发工作流?

第一章:Open-AutoGLM开源平台的核心理念与行业定位Open-AutoGLM 是一个面向通用语言模型自动化开发与部署的开源平台,致力于降低大模型应用门槛,推动AI技术在企业级场景中的普惠化落地。该平台融合了自动化机器学习(AutoML&#x…

作者头像 李华
网站建设 2026/4/18 6:49:30

手机AI性能提升90%?Open-AutoGLM优化秘籍首次公开

第一章:手机AI性能提升90%?Open-AutoGLM优化秘籍首次公开近年来,移动端大模型推理的性能瓶颈成为制约AI应用落地的关键因素。Open-AutoGLM 作为开源的轻量化推理框架,通过动态图优化与算子融合技术,首次在主流安卓设备…

作者头像 李华
网站建设 2026/4/18 5:34:18

模型自动化测试新标杆,Open-AutoGLM测试你必须知道的7个关键点

第一章:模型自动化测试新标杆,Open-AutoGLM的诞生背景随着大语言模型在自然语言处理领域的广泛应用,模型的可靠性与稳定性成为工业落地的关键瓶颈。传统人工测试方法难以覆盖复杂的语义场景,且效率低下,无法满足快速迭…

作者头像 李华
网站建设 2026/4/18 1:06:53

飞控IMU模块技术与功能详解

一、 技术要点IMU的技术要点主要体现在其硬件构成、性能指标和面临的挑战上。1. 核心传感器:陀螺仪:测量飞行器绕自身三个轴(俯仰、横滚、偏航)的角速度。现代飞控主要使用MEMS陀螺仪,其核心是一个微小的振动结构&…

作者头像 李华
网站建设 2026/4/18 6:26:33

Altium Designer教程:AD20高速信号完整性分析指南

高速PCB设计实战:用AD20搞定信号完整性,别再靠“试板”碰运气了你有没有遇到过这样的场景?一块精心布线的FPGADDR3板子,原理图反复检查无误,电源干净稳定,结果上电后就是无法初始化内存。示波器一抓时钟信号…

作者头像 李华