NestJS 容器化部署实战
概述
NestJS 后端服务的容器化与前端 SSR 项目有显著差异:需要处理 Prisma Client 生成、生产依赖安装、数据库连接配置和日志持久化。本文讲解 NestJS 应用从 Dockerfile 编写到 Docker Compose 多服务编排的完整部署方案。
前置知识
- Docker 安全性与命令实践指南
- 在 Nest 里集成 Prisma
- Docker 多阶段构建与 Compose 基础
学习目标
- 理解 NestJS 与 Nuxt3 容器化的核心差异
- 掌握含 Prisma 的 NestJS Dockerfile 编写
- 熟练配置数据库连接与日志 Volume 映射
- 能够搭建 Docker Compose 多服务生产环境
一、NestJS vs Nuxt3 容器化差异
| 维度 | NestJS(后端 API) | Nuxt3(SSR 前端) |
|---|---|---|
| 构建产物 | dist/ 目录 | .output/ 目录 |
| 运行时依赖 | 需要 npm install --omit=dev | 已打包在 .output 中 |
| Prisma | 需要生成 + 拷贝 Client | 通常不需要 |
| 数据库 | 必须配置连接 | 通常通过 API 间接访问 |
| 端口 | 3000(API 服务) | 3000(Web 服务) |
| 日志 | 需要持久化映射 | 相对轻量 |
NestJS 容器化特殊需求
图表渲染中…
二、Dockerfile 完整配置
package.json 调整
json
{
"scripts": {
"generate": "prisma generate"
}
}将 preinstall: "prisma generate" 改为手动脚本,避免构建阶段自动执行导致失败。
完整 Dockerfile
dockerfile
# ==================== 构建阶段 ====================
FROM node:18-alpine AS builder
USER node
WORKDIR /home/node/app
RUN npm config set registry https://registry.npmmirror.com
# 复制依赖配置和 Prisma Schema
COPY --chown=node:node package*.json ./
COPY --chown=node:node prisma ./prisma/
# 安装全量依赖
RUN npm install
# 生成 Prisma Client
RUN npx prisma generate
# 复制源码并构建
COPY --chown=node:node . .
RUN npm run build
# ==================== 生产阶段 ====================
FROM node:18-alpine AS production
USER node
WORKDIR /home/node/app
# 安装生产依赖
COPY --from=builder --chown=node:node /home/node/app/package*.json ./
RUN npm install --omit=dev
# 拷贝 Prisma Client(关键步骤)
COPY --from=builder --chown=node:node /home/node/app/node_modules/.prisma ./node_modules/.prisma
COPY --from=builder --chown=node:node /home/node/app/node_modules/@prisma ./node_modules/@prisma
# 拷贝构建产物
COPY --from=builder --chown=node:node /home/node/app/dist ./dist
# 拷贝 Prisma Schema(运行迁移时需要)
COPY --from=builder --chown=node:node /home/node/app/prisma ./prisma
EXPOSE 3000
ENV NODE_ENV=production
CMD ["node", "dist/main.js"]关键步骤解析
为什么必须手动拷贝 Prisma Client?
npm install --omit=dev不安装 Prisma CLI(开发依赖)- 但运行时需要
@prisma/client和.prisma/client(生成的类型) - 必须从构建阶段拷贝这两个目录
文件拷贝顺序的重要性:
package.json→ 定义依赖npm install --omit=dev→ 安装生产依赖- Prisma Client → 覆盖/补充到 node_modules
dist/→ 最后拷贝构建产物
.dockerignore
plaintext
node_modules
dist
.git
.github
.env*
*.log
.DS_Store
logs三、环境变量与数据库配置
Docker Compose + .env(推荐)
bash
# .env
DATABASE_URL="postgresql://postgres:password@postgres:5432/mydb"
JWT_SECRET="your-secret-key"
PORT=3000
NODE_ENV=production注意:DATABASE_URL 中的 host 使用 Docker 服务名 postgres(而非 localhost),Docker 内部 DNS 会自动解析。
命令行传递
bash
docker run -d --name nest-app -p 3000:3000 \
-e DATABASE_URL="postgresql://user:pass@host:5432/db" \
-e JWT_SECRET="secret" \
nest-app:1.0四、日志映射与持久化
为什么需要日志映射
容器删除后内部文件丢失,日志必须通过 Volume 持久化到宿主机。
Volume 配置
yaml
services:
nest-app:
image: nest-app:1.0
volumes:
- ./logs:/home/node/app/logs # 日志目录映射NestJS 日志配置(Winston 示例):
typescript
// 确保日志输出到文件
const logger = winston.createLogger({
transports: [
new winston.transports.File({
filename: 'logs/error.log',
level: 'error',
}),
new winston.transports.File({
filename: 'logs/combined.log',
}),
],
});五、Docker Compose 多服务编排
完整生产配置
yaml
version: '3.8'
services:
# PostgreSQL 数据库
postgres:
image: postgres:15-alpine
container_name: postgres-db
restart: always
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: mydb
ports:
- "5432:5432"
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- app-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
# Redis 缓存
redis:
image: redis:7-alpine
container_name: redis-cache
restart: always
ports:
- "6379:6379"
networks:
- app-network
# NestJS 应用
nest-app:
build:
context: .
dockerfile: Dockerfile
container_name: nest-app-prod
restart: always
ports:
- "3000:3000"
env_file:
- .env
volumes:
- ./logs:/home/node/app/logs
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
networks:
- app-network
volumes:
postgres-data:
networks:
app-network:
driver: bridgedepends_on 与健康检查
yaml
depends_on:
postgres:
condition: service_healthy # 等待数据库健康检查通过
redis:
condition: service_started # 只需启动即可这确保 NestJS 应用不会在数据库就绪前启动,避免连接失败。
六、完整部署流程
bash
# 1. 构建镜像
docker compose build
# 2. 启动所有服务
docker compose up -d
# 3. 执行数据库迁移
docker compose exec nest-app npx prisma migrate deploy
# 4. 填充初始数据(可选)
docker compose exec nest-app npx prisma db seed
# 5. 查看运行状态
docker compose ps
docker compose logs -f nest-app
# 6. 更新部署
git pull
docker compose build
docker compose up -d
docker compose exec nest-app npx prisma migrate deploy常用运维命令
bash
docker compose ps # 服务状态
docker compose logs -f nest-app # 实时日志
docker compose exec nest-app sh # 进入容器
docker compose restart nest-app # 重启应用
docker compose down # 停止所有
docker compose down -v # 停止并删除数据卷
docker compose exec postgres psql -U postgres -d mydb # 连接数据库常见问题
数据库连接失败
| 原因 | 解决方案 |
|---|---|
| host 使用 localhost | 改为 Docker 服务名(如 postgres) |
| 数据库未就绪 | 配置 depends_on + healthcheck |
| 密码/端口错误 | 检查 .env 与 postgres 服务配置一致 |
| 网络不通 | 确保在同一 networks 下 |
Prisma Client 找不到
bash
# 确认构建阶段执行了 prisma generate
# 确认生产阶段拷贝了 .prisma 和 @prisma 目录
docker compose exec nest-app ls node_modules/.prisma/client容器启动即退出
bash
docker compose logs nest-app # 查看错误
# 常见原因:dist/main.js 路径错误、环境变量缺失、端口冲突最佳实践
安全配置清单
| 项目 | 要求 |
|---|---|
| 用户 | 非 root(USER node) |
| 文件权限 | COPY --chown=node:node |
| 敏感配置 | env_file 注入,.env 加入 .gitignore |
| 数据库密码 | 不使用默认密码,生产用 Docker Secret |
| 网络 | 内部服务不暴露不必要端口 |
性能优化
- 使用 alpine 镜像减小体积
- 多阶段构建排除开发依赖
- PostgreSQL 数据使用 named volume 持久化
- 配置
restart: always确保异常自动恢复 - 日志文件定期轮转,避免磁盘占满
监控与告警
yaml
# 健康检查配置
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 15s