第 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 编译器、测试框架)带入生产镜像。
业务场景
- 微服务部署:每个服务独立容器,通过 Docker Compose 或 K8s 编排
- 开发环境一致:新成员 clone 仓库后
docker compose up即可启动 - CI/CD 流水线:构建镜像 → 推送仓库 → 部署到服务器
- 自动伸缩:Kubernetes 根据负载自动增减容器实例
扩展阅读
上一章:第 21 章 · 性能优化 下一章:第 23 章 · CI/CD — GitHub Actions、自动化测试和部署。