Featured image of post 通过多阶段构建减少 Docker 镜像大小Featured image of post 通过多阶段构建减少 Docker 镜像大小

通过多阶段构建减少 Docker 镜像大小

了解如何使用 Docker 多阶段构建将编译时工具与生产运行时分离,以优化容器占用空间和安全性。

介绍

保持 Docker 镜像占用空间较小对于加速​​部署周期、降低存储成本以及通过缩小容器的攻击面来加强安全性至关重要。

然而,简单的 Dockerfile 构造通常将编译时依赖项(如 gcc、标头、构建缓存)和测试工具直接捆绑到最终的运行时映像中。这会使图像大小从几兆字节膨胀到数百或千兆字节。

为了解决这个问题,Docker 引入了多阶段构建。本文回顾了多阶段架构的核心实践,展示了使用 TypeScript Node.js 项目进行图像占用空间优化。


1. 什么是多阶段构建?

多阶段构建允许您在单个 Dockerfile 中使用多个 FROM 语句。每个 FROM 语句都会使用新的基础映像启动一个新的、独立的“阶段”。

您可以有选择地将工件(例如编译的二进制文件或缩小的包)从上一个阶段复制到下一个阶段。这允许您放弃繁重的构建依赖项(编译器、源代码、linter),并仅将运行时需求打包到最终容器中。

[Build Stage]
・Base: node (large)
・Install devDependencies
・Compile source (npm run build)
          ▼ (Copy only compiled JS & prod dependencies)
[Runtime Stage]
・Base: alpine (minimal)
・Copy compiled output
・CMD Execution

2. 未优化与多阶段 Dockerfile

让我们将传统 Dockerfile 与 TypeScript 应用程序的多阶段 Dockerfile 进行比较。

未优化的方法(重图像)

# Uses a full Node image containing compilation environments
FROM node:20

WORKDIR /app

# Restore dependencies
COPY package*.json ./
RUN npm install

# Copy source and compile (TypeScript -> JS)
COPY . .
RUN npm run build

# Start the application
EXPOSE 3000
CMD ["node", "dist/index.js"]
  • 问题: 生成的映像包括 TypeScript 编译器(devDependencies 内的 typescript 包)、未编译的 .ts 源文件、缓存有效负载和重型构建工具。最终图像大小通常超过 900MB

多级解决方案(Slim Image)

# -----------------------------------------------
# Stage 1: Build Environment (builder)
# -----------------------------------------------
FROM node:20-alpine AS builder

WORKDIR /app

COPY package*.json ./
# Install all dependencies including devDependencies
RUN npm ci

COPY . .
# Transpile TypeScript to JavaScript
RUN npm run build

# Clean up devDependencies to leave only production modules
RUN npm prune --production

# -----------------------------------------------
# Stage 2: Runtime Environment (runner)
# -----------------------------------------------
# Use a minimal, slim OS base (Alpine Linux)
FROM node:20-alpine AS runner

WORKDIR /app
ENV NODE_ENV=production

# Copy only the compiled code and production node_modules from the builder stage
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist

EXPOSE 3000
CMD ["node", "dist/index.js"]

3. 为什么图像足迹急剧缩小

实施这种多阶段设置实现了两个主要优化:

  • 消除开发依赖: 运行 npm prune --production 会去掉节点开发包(例如打字稿编译器和 linter),只留下必要的生产库。
  • 采用Alpine操作系统: 使用 node:alpine 替换基于 Debian 的节点映像可减少基准操作系统占用空间。
  • 结果: 最终容器占用空间从 900MB 降至 低于 150MB

4. 减少足迹的高级技巧

编写 .dockerignore 文件

为了防止大量本地目录或敏感值进入构建上下文,请在根文件夹中创建一个 .dockerignore 文件:

node_modules
.git
.github
npm-debug.log
dist
.env

层缓存调整

始终将依赖项安装步骤(COPY package*.jsonRUN npm install)放在之前复制源代码(COPY . .)。这可确保 Docker 缓存依赖层,仅在源文件发生更改时跳过包重新安装。

结论

多阶段构建是优化容器操作的基本技术:

  1. 命名您的构建阶段(例如 FROM ... AS builder)。
  2. 使用 COPY --from 仅检索所需的文件。
  3. 使用轻量级基础操作系统,如 Alpine Linux。

在项目中采用多阶段 Dockerfile 来加速 CI/CD 管道并保护生产环境。