首页技术专题博客目录关于与联系

Docker 镜像从 2GB 优化到 200MB 的过程

Docker 镜像太大,拉取慢、构建慢、部署慢。通过换基础镜像、多阶段构建等方法,从 2GB 优化到 200MB。

上周五下午,运维找我:"你们的镜像怎么这么大?2.1GB,拉取要10分钟,能不能优化一下?"

我看了下,确实大得离谱。然后花了一天时间,把镜像从 2GB 优化到 200MB。

问题在哪

先看原来的 Dockerfile:

FROM node:18 WORKDIR /app COPY . . RUN npm install RUN npm run build CMD ["node", "dist/server.js"]

看起来没问题,对吧?问题大了。

我用 docker history 查看各层大小:

IMAGE SIZE node:18 1.1GB # 基础镜像 COPY . . 500MB # 源码 + node_modules + .git npm install 300MB # 又装了一遍依赖 npm run build 200MB # 构建产物 + 缓存

总共 2.1GB。

优化过程

第一步:换基础镜像

node:18 是完整的 Debian 系统,1.1GB。我们只需要运行 Node.js,不需要这么多东西。

换成 Alpine:

FROM node:18-alpine

Alpine 只有 170MB,直接省了 1GB。

第二步:.dockerignore

原来的 COPY . . 把所有文件都复制进去了,包括:

  • node_modules/ (300MB)
  • .git/ (150MB)
  • *.log
  • .env

创建 .dockerignore

node_modules .git *.log .env .DS_Store coverage .vscode

COPY 的体积从 500MB 降到 50MB。

第三步:多阶段构建

我们需要 npm install 来构建,但运行时不需要 devDependencies。

改成多阶段:

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

最终镜像里只有:

  • Alpine 基础镜像 (170MB)
  • 生产依赖 (20MB)
  • 构建产物 (10MB)

总共 200MB。

第四步:利用缓存

注意到 COPY package*.jsonCOPY . . 之前。

这样,如果 package.json 没变,npm ci 这层就会用缓存,不用重新下载依赖。

改代码不会触发 npm install,构建速度快很多。

第五步:精简依赖

我顺便检查了 package.json,发现很多没用的包:

  • lodash:只用了2个方法,可以自己写
  • moment:可以用原生 Datedate-fns
  • axios:Node.js 18 有原生 fetch

删掉这些包,又省了 10MB。

最终结果

指标优化前优化后
镜像大小2.1GB190MB
拉取时间10分钟30秒
构建时间5分钟1分钟
存储成本$50/月$5/月

缩小了 91%

其他技巧

1. 合并 RUN 命令

每个 RUN 都会创建一层。如果有多个 RUN,可以合并:

# 不好 RUN apt-get update RUN apt-get install -y curl RUN apt-get clean # 好 RUN apt-get update && \ apt-get install -y curl && \ apt-get clean && \ rm -rf /var/lib/apt/lists/*

2. 使用 distroless

如果是 Go、Java 这种编译型语言,可以用 gcr.io/distroless/static

这个镜像只有 2MB,连 shell 都没有,非常安全。

3. 压缩构建产物

对于前端项目,可以在构建时开启 gzip/brotli 压缩。

RUN npm run build && \ find dist -type f \( -name '*.js' -o -name '*.css' \) \ -exec gzip -k {} \;

总结

优化 Docker 镜像的核心原则:

  1. 选对基础镜像:能用 Alpine 就别用完整版
  2. 减少复制内容:用 .dockerignore
  3. 多阶段构建:构建和运行分离
  4. 利用缓存:把不常变的层放前面
  5. 精简依赖:定期清理无用的包

这些都是小改动,但效果立竿见影。

你的镜像有多大?有没有优化的空间?


参考资料:Docker 最佳实践

评论区