介绍
保持 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*.json 和 RUN npm install)放在之前复制源代码(COPY . .)。这可确保 Docker 缓存依赖层,仅在源文件发生更改时跳过包重新安装。
结论
多阶段构建是优化容器操作的基本技术:
- 命名您的构建阶段(例如
FROM ... AS builder)。 - 使用
COPY --from仅检索所需的文件。 - 使用轻量级基础操作系统,如 Alpine Linux。
在项目中采用多阶段 Dockerfile 来加速 CI/CD 管道并保护生产环境。

