强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第 22 章 · Docker 部署

第 22 章 · Docker 部署

22.1 Docker 基础

为什么用 Docker

问题没有 Docker有 Docker
环境差异“在我机器上能跑”容器环境一致
依赖冲突全局安装相互干扰隔离的文件系统
部署流程手动 SSH + 配置一条命令部署
扩展手动启停实例容器编排自动伸缩

核心概念

概念说明
镜像(Image)只读模板,包含应用和依赖
容器(Container)镜像的运行实例
Dockerfile构建镜像的脚本
Volume持久化存储
Network容器间网络通信

22.2 Dockerfile

基础 Dockerfile

# 基础镜像
FROM node:22-alpine

# 设置工作目录
WORKDIR /app

# 复制依赖文件(利用缓存层)
COPY package.json package-lock.json ./

# 安装依赖
RUN npm ci --only=production

# 复制源代码
COPY . .

# 暴露端口
EXPOSE 3000

# 设置环境变量
ENV NODE_ENV=production

# 启动命令
CMD ["node", "src/server.js"]

多阶段构建(推荐)

# === 阶段 1:安装依赖 ===
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci

# === 阶段 2:构建(如有 TypeScript 等)===
FROM node:22-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

# === 阶段 3:生产运行 ===
FROM node:22-alpine AS runner
WORKDIR /app

# 安全:使用非 root 用户
RUN addgroup --system --gid 1001 nodejs && \
    adduser --system --uid 1001 appuser

# 只复制生产依赖
COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY package.json ./

USER appuser
EXPOSE 3000
ENV NODE_ENV=production

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

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

.dockerignore

node_modules
npm-debug.log
.git
.gitignore
.env
.env.local
Dockerfile
docker-compose.yml
coverage
tests
*.md
.vscode
.DS_Store
dist

22.3 Docker Compose

# docker-compose.yml
version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://user:pass@db:5432/mydb
      - REDIS_URL=redis://redis:6379
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    restart: unless-stopped
    volumes:
      - ./logs:/app/logs

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

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redisdata:/data

volumes:
  pgdata:
  redisdata:
# 常用命令
docker compose up -d          # 启动(后台)
docker compose down           # 停止
docker compose logs -f app    # 查看日志
docker compose ps             # 查看状态
docker compose exec app sh    # 进入容器
docker compose up -d --build  # 重新构建并启动

22.4 PM2 进程管理

不使用 Docker 时的部署

# 安装
npm install -g pm2

# 启动应用
pm2 start src/server.js --name my-app

# 集群模式
pm2 start src/server.js -i max --name my-app

# 使用配置文件
pm2 start ecosystem.config.js

# 管理命令
pm2 status                  # 查看状态
pm2 logs my-app             # 查看日志
pm2 restart my-app          # 重启
pm2 reload my-app           # 零停机重启
pm2 stop my-app             # 停止
pm2 delete my-app           # 删除
pm2 monit                   # 实时监控

# 开机自启
pm2 startup
pm2 save

ecosystem.config.js

module.exports = {
  apps: [{
    name: 'my-app',
    script: 'src/server.js',
    instances: 'max',
    exec_mode: 'cluster',
    
    // 环境变量
    env: {
      NODE_ENV: 'development',
      PORT: 3000,
    },
    env_production: {
      NODE_ENV: 'production',
      PORT: 3000,
    },

    // 内存限制自动重启
    max_memory_restart: '1G',

    // 日志配置
    error_file: './logs/pm2-error.log',
    out_file: './logs/pm2-out.log',
    log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
    merge_logs: true,

    // 自动重启配置
    autorestart: true,
    watch: false,           // 生产环境不要开启
    max_restarts: 10,       // 15 分钟内最多重启 10 次
    min_uptime: '10s',      // 运行超过 10 秒才算启动成功

    // 优雅退出
    kill_timeout: 5000,
    listen_timeout: 10000,
    shutdown_with_message: true,
  }],
};

22.5 Docker 镜像优化

优化技巧

技巧说明
使用 Alpine 基础镜像node:22-alpine 约 50MB,node:22 约 350MB
利用构建缓存先 COPY package.json,再 COPY 源码
多阶段构建开发依赖不进入生产镜像
使用 .dockerignore排除 node_modules 等无关文件
合并 RUN 指令减少镜像层数
使用 npm ci比 npm install 更快更可靠
# 查看镜像大小
docker images my-app

# 分析镜像层
docker history my-app:latest

# 扫描安全漏洞
docker scout cves my-app:latest

22.6 生产部署检查清单

# 1. 使用特定版本标签
FROM node:22.12.0-alpine3.20

# 2. 使用非 root 用户
USER node

# 3. 设置健康检查
HEALTHCHECK CMD curl -f http://localhost:3000/health || exit 1

# 4. 使用 NODE_ENV=production
ENV NODE_ENV=production

# 5. 只安装生产依赖
RUN npm ci --only=production && npm cache clean --force

# 6. 信号处理
# 使用 exec 形式确保 Node.js 是 PID 1
CMD ["node", "src/server.js"]

注意事项

⚠️ 不要在镜像中存储密钥:使用环境变量、Docker Secrets 或外部密钥管理。

⚠️ 不要使用 latest 标签:使用具体版本号确保构建的可重复性。

⚠️ 注意信号处理:确保 Node.js 是 PID 1,才能正确接收 SIGTERM。

⚠️ 多阶段构建:不要将开发工具(TypeScript 编译器、测试框架)带入生产镜像。

业务场景

  1. 微服务部署:每个服务独立容器,通过 Docker Compose 或 K8s 编排
  2. 开发环境一致:新成员 clone 仓库后 docker compose up 即可启动
  3. CI/CD 流水线:构建镜像 → 推送仓库 → 部署到服务器
  4. 自动伸缩:Kubernetes 根据负载自动增减容器实例

扩展阅读


上一章第 21 章 · 性能优化 下一章第 23 章 · CI/CD — GitHub Actions、自动化测试和部署。