Docker 容器化最佳实践:从开发到生产

Docker 彻底改变了我们构建、交付和运行应用程序的方式。但要写出高效的 Dockerfile,让镜像既小又安全,需要遵循一系列最佳实践。

1. 选择正确的基础镜像

基础镜像的选择直接影响最终镜像的大小和安全性。

使用官方镜像

# ✅ 推荐:官方镜像,定期更新
FROM node:20-alpine

# ❌ 避免:来源不明的镜像
FROM some-random-user/node:latest

Alpine vs Slim vs Full

镜像类型 大小 适用场景
node:20 ~1GB 不需要优化
node:20-slim ~250MB 通用推荐
node:20-alpine ~120MB 追求极致体积

Alpine 使用 musl libc 而非 glibc,某些原生模块可能不兼容。

2. 多阶段构建

多阶段构建是减小镜像体积的最强武器:

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

# 阶段 2:运行
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]

3. 利用构建缓存

Docker 按层缓存,将不常变动的指令放在前面:

# ✅ 正确顺序:依赖安装在前
COPY package*.json ./
RUN npm ci
COPY . .

# ❌ 错误顺序:源码变化导致依赖重新安装
COPY . .
RUN npm ci

.dockerignore 不可忽视

node_modules
.git
dist
.env
*.md
.DS_Store
coverage

4. 以非 root 用户运行

FROM node:20-alpine

# 创建非 root 用户
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001

USER nodejs

COPY --chown=nodejs:nodejs . /app
WORKDIR /app

CMD ["node", "index.js"]

5. 健康检查

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1

6. 使用 docker-compose 管理多服务应用

version: '3.8'
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
      target: production
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://user:pass@db:5432/mydb
    depends_on:
      db:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "wget", "--spider", "http://localhost:3000/health"]
      interval: 30s

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: mydb
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user"]
      interval: 10s

volumes:
  pgdata:

7. 日志管理

应用日志应输出到 stdout/stderr,由 Docker 或容器编排平台统一处理:

// ✅ 好的做法
console.log(JSON.stringify({
  level: 'info',
  message: 'User logged in',
  userId: 123,
  timestamp: new Date().toISOString()
}));

// ❌ 避免
fs.appendFileSync('/var/log/app.log', 'User logged in\n');

8. 安全扫描

# 使用 Docker Scout 扫描漏洞
docker scout quickview myapp:latest

# 使用 Trivy
trivy image myapp:latest

# 使用 Hadolint 检查 Dockerfile
hadolint Dockerfile

总结

高效的 Docker 实践可以总结为:

  1. 使用官方、轻量级基础镜像
  2. 多阶段构建减小体积
  3. 合理安排 COPY 顺序利用缓存
  4. 以非 root 用户运行
  5. 添加健康检查
  6. 将日志输出到 stdout/stderr
  7. 定期安全扫描

记住,一个好的 Dockerfile 不仅是能构建出镜像,更要让镜像小、安全、可维护