镜像体积大,意味着拉取慢、发布慢、节点磁盘占用高,镜像里残留的编译工具和源码还会扩大攻击面。本文用一个 Go 服务和一个 Node.js 服务的例子,说明三件最有效的事:多阶段构建、合理安排层缓存、用好 .dockerignore。
docker images myapp
docker history myapp:latestdocker history 会列出每一层的大小和对应的指令,可以直接看出是哪条指令引入了大量文件,比如 apt-get install 后没有清理缓存,或者 COPY . . 把整个工程拷了进来。
思路:构建阶段用带编译工具的完整镜像,运行阶段只拷贝产物。Go 服务示例:
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,需要选择带对应库的基础镜像。Node.js 服务示例:
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 按指令顺序构建,某一层的输入变化后,其后的所有层缓存都会失效。把不常变的放前面,常变的放后面:
package.json、go.mod、requirements.txt),安装依赖。COPY . . 拷贝源码。这样只改业务代码时,依赖安装层可以直接命中缓存,构建速度明显提升。反过来先 COPY . . 再装依赖,每次改代码都会重装全部依赖。
文件一旦写入某一层,后续层删除它也不会减小镜像大小,因为旧层仍在。所以安装和清理要放在同一条 RUN 里:
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl ca-certificates \
&& rm -rf /var/lib/apt/lists/*--no-install-recommends 能减少不必要的推荐包。
构建时会把上下文目录发送给 Docker,.dockerignore 可以排除不需要的文件,既加快构建,也避免把敏感文件拷进镜像:
cat > .dockerignore <<'EOF'
.git
node_modules
*.log
.env
dist
Dockerfile
.dockerignore
EOF注意 .env、密钥文件、.git 目录不要进入镜像。密钥不要通过 ARG 或 ENV 写进镜像层,构建期需要的凭据可以使用 BuildKit 的 secret 挂载。
RUN --mount=type=cache,target=/root/.cache/go-build 可以保留编译缓存。docker image prune 清理悬空镜像,注意确认没有依赖这些镜像的容器。docker history 找出大的层。.dockerignore 避免无关和敏感文件进入镜像。原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。