Skip to content

镜像构建最佳实践

1. 选择合适的基础镜像

优先使用官方镜像

dockerfile
# 推荐:官方维护,安全更新及时
FROM node:20-alpine
FROM python:3.12-slim
FROM nginx:alpine

# 不推荐:非官方镜像,来源不明
FROM someuser/node-custom

选择最小化基础镜像

镜像大小适用场景
ubuntu:22.04~77MB需要完整系统工具
debian:slim~75MB轻量 Debian 环境
alpine:3.19~7MB最小化场景
distroless~2MB安全生产环境
scratch0静态编译程序
dockerfile
# 不推荐:体积大
FROM ubuntu:22.04

# 推荐:Alpine,体积小
FROM node:20-alpine

# 推荐:slim 版本
FROM python:3.12-slim

固定镜像版本

dockerfile
# 不推荐:latest 标签可能引入破坏性变更
FROM node:latest

# 推荐:固定具体版本,确保可重现
FROM node:20.11.0-alpine3.19
FROM python:3.12.2-slim-bookworm

2. 合理利用构建缓存

Docker 构建时,从变化的层开始重新执行,之前的层使用缓存。

将变化频率低的操作放前面

dockerfile
# 不推荐:代码变化会导致依赖重新安装
FROM node:20-alpine
WORKDIR /app
COPY . .                     # 代码经常变化
RUN npm install              # 每次都重新安装

# 推荐:先复制依赖文件,再复制代码
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./        # 依赖变化频率低
RUN npm install              # 依赖不变时使用缓存
COPY . .                     # 代码变化只影响这层及以后

分离不同类型的文件

dockerfile
FROM python:3.12-slim
WORKDIR /app

# 依赖文件(变化少)
COPY requirements.txt .
RUN pip install -r requirements.txt

# 配置文件(偶尔变化)
COPY config/ ./config/

# 应用代码(经常变化)
COPY src/ ./src/

3. 减少镜像层数

每条 RUN 指令创建一个新层,合并减少层数:

dockerfile
# 不推荐:多条 RUN 创建多个层
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y vim
RUN rm -rf /var/lib/apt/lists/*

# 推荐:合并成一条 RUN
RUN apt-get update \
    && apt-get install -y \
       curl \
       vim \
    && rm -rf /var/lib/apt/lists/*

4. 清理缓存减小镜像大小

安装软件后清理包管理器缓存:

dockerfile
# APT(Debian/Ubuntu)
RUN apt-get update \
    && apt-get install -y --no-install-recommends \
       curl \
       ca-certificates \
    && rm -rf /var/lib/apt/lists/*

# YUM/DNF(CentOS/RHEL)
RUN yum install -y curl \
    && yum clean all \
    && rm -rf /var/cache/yum

# Alpine APK
RUN apk add --no-cache curl

# pip
RUN pip install --no-cache-dir -r requirements.txt

# npm
RUN npm ci --only=production && npm cache clean --force

5. 使用 .dockerignore

# .dockerignore
.git/
.gitignore
node_modules/
dist/
build/
coverage/
*.log
.env
.env.local
.DS_Store
README.md
docs/
tests/
__pycache__/
*.pyc
*.pyo
.pytest_cache/
.venv/

好处:

  • 减少构建上下文大小,加快构建速度
  • 避免敏感文件(.env)进入镜像
  • 避免 node_modules 等大目录不必要地复制

6. 以非 root 用户运行

dockerfile
# Alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

# Debian/Ubuntu
RUN groupadd --system appgroup \
    && useradd --system --group appgroup appuser
USER appuser

# 使用已存在的用户(Node.js 官方镜像有 node 用户)
USER node

7. 设置有意义的 LABEL

dockerfile
LABEL org.opencontainers.image.title="My Application" \
      org.opencontainers.image.description="A brief description" \
      org.opencontainers.image.version="1.0.0" \
      org.opencontainers.image.authors="team@example.com" \
      org.opencontainers.image.url="https://example.com" \
      org.opencontainers.image.source="https://github.com/org/repo" \
      org.opencontainers.image.created="2024-01-01T00:00:00Z"

8. 正确使用 COPY 和 ADD

dockerfile
# 推荐:COPY 更透明
COPY package.json .
COPY src/ ./src/

# 仅在需要解压时用 ADD
ADD archive.tar.gz /app/

# 不推荐:用 ADD 下载 URL(难以缓存和验证)
ADD https://example.com/file.txt /app/
# 推荐替代方案:
RUN curl -fsSL https://example.com/file.txt -o /app/file.txt

9. 优化 CMD 和 ENTRYPOINT

dockerfile
# 不推荐:shell 格式无法接收信号
CMD node app.js

# 推荐:exec 格式,可以正确接收 SIGTERM
CMD ["node", "app.js"]

# 推荐:使用 ENTRYPOINT + CMD 提供灵活性
ENTRYPOINT ["node"]
CMD ["app.js"]

10. 添加健康检查

dockerfile
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

完整最佳实践示例

dockerfile
# 阶段一:安装依赖
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production \
    && npm cache clean --force

# 阶段二:构建
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# 阶段三:生产镜像
FROM node:20-alpine AS production

LABEL org.opencontainers.image.title="My App" \
      org.opencontainers.image.version="1.0.0"

ENV NODE_ENV=production \
    PORT=3000

# 创建非 root 用户
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

WORKDIR /app

# 从前面的阶段复制
COPY --from=deps --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist

# 只复制需要的文件
COPY --chown=appuser:appgroup package.json .

USER appuser

EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=10s --start-period=15s --retries=3 \
  CMD wget -qO- http://localhost:3000/health || exit 1

CMD ["node", "dist/main.js"]

检查镜像质量

bash
# 查看镜像大小
docker images myapp

# 查看镜像层详情
docker history --no-trunc myapp

# 使用 dive 工具分析镜像(需单独安装)
dive myapp

# 使用 docker scout 扫描漏洞(Docker Desktop 内置)
docker scout cves myapp

总结

镜像构建最佳实践清单:

  • [ ] 使用官方基础镜像,固定具体版本
  • [ ] 优先选择 alpine 或 slim 版本
  • [ ] 使用多阶段构建分离构建和运行环境
  • [ ] 合理安排指令顺序,最大化缓存利用
  • [ ] 合并 RUN 指令,安装后清理缓存
  • [ ] 配置 .dockerignore 排除无关文件
  • [ ] 使用非 root 用户运行应用
  • [ ] 添加有意义的 LABEL
  • [ ] CMD/ENTRYPOINT 使用 exec 格式
  • [ ] 添加 HEALTHCHECK

下一节介绍镜像优化与瘦身的进阶技巧。