首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Docker 镜像瘦身实践:多阶段构建、层缓存顺序与 .dockerignore

Docker 镜像瘦身实践:多阶段构建、层缓存顺序与 .dockerignore

原创
作者头像
一只特立独行的兔先生
发布于 2026-11-01 12:29:00
发布于 2026-11-01 12:29:00
1590
举报

背景

镜像体积大,意味着拉取慢、发布慢、节点磁盘占用高,镜像里残留的编译工具和源码还会扩大攻击面。本文用一个 Go 服务和一个 Node.js 服务的例子,说明三件最有效的事:多阶段构建、合理安排层缓存、用好 .dockerignore。

一、先看镜像大在哪里

代码语言:bash
复制
docker images myapp
docker history myapp:latest

docker history 会列出每一层的大小和对应的指令,可以直接看出是哪条指令引入了大量文件,比如 apt-get install 后没有清理缓存,或者 COPY . . 把整个工程拷了进来。

二、多阶段构建

思路:构建阶段用带编译工具的完整镜像,运行阶段只拷贝产物。Go 服务示例:

代码语言:dockerfile
复制
FROM golang:1.22 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /out/server ./cmd/server

FROM gcr.io/distroless/static-debian12
COPY --from=build /out/server /server
USER nonroot
ENTRYPOINT ["/server"]

要点:

  • 第一个阶段包含编译器和依赖,最终镜像不包含它们。
  • CGO_ENABLED=0 生成静态二进制,才能放进不带 libc 的极简基础镜像。如果程序依赖 cgo,需要选择带对应库的基础镜像。
  • 极简基础镜像没有 shell 和包管理器,排查问题不方便,可以在调试时使用同版本带 shell 的变体,或使用临时调试容器。

Node.js 服务示例:

代码语言:dockerfile
复制
FROM node:20 AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev

FROM node:20-slim
WORKDIR /app
ENV NODE_ENV=production
COPY --from=deps /app/node_modules ./node_modules
COPY . .
USER node
CMD ["node", "server.js"]

如果项目需要先构建前端或 TypeScript,再增加一个 build 阶段,最终阶段只拷贝构建产物和生产依赖。

三、基础镜像怎么选

  • slim 变体:基于 Debian,去掉了大量非必需软件,兼容性好,体积适中。
  • alpine:体积小,但使用 musl libc,部分依赖原生扩展的软件(例如某些 Python、Node 包)可能出现兼容或性能问题,需要实测。
  • distroless:只包含运行时,没有 shell,体积小且攻击面小,适合静态编译的程序。
  • 固定具体版本标签,不要用 latest,保证构建可复现。

四、安排好指令顺序,利用层缓存

Docker 按指令顺序构建,某一层的输入变化后,其后的所有层缓存都会失效。把不常变的放前面,常变的放后面:

  1. 先拷贝依赖清单(package.json、go.mod、requirements.txt),安装依赖。
  2. 最后再 COPY . . 拷贝源码。

这样只改业务代码时,依赖安装层可以直接命中缓存,构建速度明显提升。反过来先 COPY . . 再装依赖,每次改代码都会重装全部依赖。

五、合并并清理同一层的操作

文件一旦写入某一层,后续层删除它也不会减小镜像大小,因为旧层仍在。所以安装和清理要放在同一条 RUN 里:

代码语言:dockerfile
复制
RUN apt-get update \
    && apt-get install -y --no-install-recommends curl ca-certificates \
    && rm -rf /var/lib/apt/lists/*

--no-install-recommends 能减少不必要的推荐包。

六、使用 .dockerignore

构建时会把上下文目录发送给 Docker,.dockerignore 可以排除不需要的文件,既加快构建,也避免把敏感文件拷进镜像:

代码语言:bash
复制
cat > .dockerignore <<'EOF'
.git
node_modules
*.log
.env
dist
Dockerfile
.dockerignore
EOF

注意 .env、密钥文件、.git 目录不要进入镜像。密钥不要通过 ARG 或 ENV 写进镜像层,构建期需要的凭据可以使用 BuildKit 的 secret 挂载。

七、其他实用技巧

  1. 使用 BuildKit(新版本 Docker 默认启用),支持并行构建和缓存挂载,例如 RUN --mount=type=cache,target=/root/.cache/go-build 可以保留编译缓存。
  2. 只安装生产依赖,删除测试和文档文件。
  3. 用 docker image prune 清理悬空镜像,注意确认没有依赖这些镜像的容器。
  4. 在 CI 中记录镜像大小,大小突增时告警。
  5. 以非 root 用户运行,减少容器被入侵后的影响。

小结

  1. 用 docker history 找出大的层。
  2. 多阶段构建把编译工具留在构建阶段。
  3. 依赖清单在前、源码在后,充分利用缓存。
  4. 安装与清理放在同一层,配合 .dockerignore 避免无关和敏感文件进入镜像。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 背景
  • 一、先看镜像大在哪里
  • 二、多阶段构建
  • 三、基础镜像怎么选
  • 四、安排好指令顺序,利用层缓存
  • 五、合并并清理同一层的操作
  • 六、使用 .dockerignore
  • 七、其他实用技巧
  • 小结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档