NestJS容器化部署实战
NestJS容器化部署实战
学习目标:掌握 NestJS 应用的 Docker 容器化部署、Prisma 集成、环境变量配置、日志映射、Docker Compose 多服务管理。
一、NestJS 与 Nuxt3 容器化差异
1.1 核心差异对比
code
NestJS vs Nuxt3 容器化对比:
│
├── 构建产物
│ ├── Nuxt3:.output 目录(自包含 node_modules)
│ └── NestJS:dist 目录(需要外部 node_modules)
│
├── 依赖管理
│ ├── Nuxt3:bundleDependencies 自动打包依赖
│ └── NestJS:需要手动安装生产依赖
│
├── Prisma 集成
│ ├── Nuxt3:通常无数据库操作
│ └── NestJS:需要 prisma generate 生成 client
│
├── 启动方式
│ ├── Nuxt3:node .output/server/index.mjs
│ └── NestJS:node dist/main.js
│
└── 环境变量
├── Nuxt3:BASE_URL 等 API 地址
└── NestJS:DATABASE_URL、JWT_SECRET 等1.2 NestJS 容器化特殊需求
code
NestJS 容器化关键点:
│
├── 1. Prisma Client 生成
│ ├── 执行 prisma generate
│ ├── 生成 node_modules/.prisma/client
│ └── 数据库操作必需文件
│
├── 2. 生产依赖安装
│ ├── npm install --production
│ ├── 或 npm install --omit=dev
│ └── 不包含开发依赖
│
├── 3. 文件拷贝顺序
│ ├── 先拷贝 package.json
│ ├── 安装生产依赖
│ ├── 拷贝 prisma client
│ └── 最后拷贝 dist 目录
│
└── 4. 数据库连接
├── DATABASE_URL 环境变量
├── 数据库服务运行
└── 网络连通性二、Dockerfile 配置详解
2.1 package.json 脚本调整
问题分析
json
// 原始 package.json
{
"scripts": {
"preinstall": "prisma generate"
}
}问题:
preinstall会在npm install前自动执行- 构建阶段可能没有 prisma client 类型文件
- 导致安装失败或找不到类型
解决方案
json
// 调整后的 package.json
{
"scripts": {
"generate": "prisma generate"
}
}说明:
- 将
preinstall改为手动脚本generate - 在合适的构建阶段手动执行
- 避免自动执行导致的问题
2.2 完整 Dockerfile 配置
dockerfile
# ==================== 构建阶段 ====================
FROM node:18-alpine AS builder
# 使用 node 用户(安全配置)
USER node
WORKDIR /home/node/app
# 设置淘宝源(国内加速)
RUN npm config set registry https://registry.npmmirror.com
# 复制依赖配置文件
COPY --chown=node:node package*.json ./
COPY --chown=node:node prisma ./prisma/
# 安装所有依赖(包括开发依赖)
RUN npm install
# 生成 Prisma Client
RUN npm run generate
# 或直接执行:npx prisma generate
# 复制源码
COPY --chown=node:node . .
# 构建应用
RUN npm run build
# ==================== 生产阶段 ====================
FROM node:18-alpine AS production
USER node
WORKDIR /home/node/app
# 复制 package.json
COPY --from=builder --chown=node:node /home/node/app/package*.json ./
# 安装生产依赖
RUN npm install --production
# 或使用: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"]2.3 关键配置详解
1. Prisma Client 拷贝
为什么需要手动拷贝?
code
Prisma Client 文件结构:
│
├── node_modules/.prisma/client/
│ ├── index.js # 入口文件
│ ├── schema.prisma # Schema 副本
│ ├── runtime/ # 运行时库
│ └── default.js # 默认导出
│
└── node_modules/@prisma/client/
├── index.js
└── ...(符号链接)原因:
npm install --production不会安装开发依赖- Prisma CLI 是开发依赖,不会安装
- 但运行时需要 Prisma Client
- 必须从构建阶段拷贝
2. 生产依赖安装
bash
# 两种方式等价
npm install --production
npm install --omit=dev
# 区别
# --production:npm 6+ 支持
# --omit=dev:npm 8.10+ 支持,更语义化3. 文件拷贝顺序的重要性
code
文件拷贝顺序(关键):
│
├── 1. package.json
│ └── 定义依赖版本
│
├── 2. npm install --production
│ └── 安装生产依赖到 node_modules
│
├── 3. 拷贝 Prisma Client
│ └── 覆盖/添加到 node_modules
│
└── 4. 拷贝 dist 目录
└── 最后拷贝构建产物
为什么这个顺序很重要?
├── 如果先拷贝 dist,再安装依赖
│ └── 可能覆盖某些文件(极少见)
│
└── 正确顺序确保依赖完整
└── Prisma Client 正确集成2.4 .dockerignore 配置
plaintext
# .dockerignore
node_modules
dist
.git
.github
.env*
*.log
.DS_Store
logs三、环境变量配置
3.1 数据库连接配置
方式一:命令行传递
bash
# 停止并删除旧容器
docker stop test
docker rm test
# 使用环境变量运行
docker run \
-d \
--name nest-app \
-p 8000:3000 \
-e DATABASE_URL="postgresql://user:password@host:5432/db" \
nest-app:1.0方式二:Docker Compose + .env 文件(推荐)
.env 文件:
bash
# .env
DATABASE_URL="postgresql://postgres:password@postgres:5432/mydb"
JWT_SECRET="your-secret-key"
PORT=3000
NODE_ENV=productiondocker-compose.yml:
yaml
version: '3.8'
services:
# 数据库服务
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
# NestJS 应用
nest-app:
image: nest-app:1.0
container_name: nest-app-prod
restart: always
ports:
- "8000:3000"
env_file:
- .env # 引用环境变量文件
depends_on:
- postgres
networks:
- app-network
volumes:
postgres-data:
networks:
app-network:
driver: bridge3.2 环境变量对比
| 方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
-e 参数 | 测试、临时部署 | 简单快速 | 参数多时冗长 |
environment | 简单项目 | 配置集中 | 敏感信息暴露 |
env_file | 生产环境 | 安全、可维护 | 需要额外文件 |
四、日志映射与持久化
4.1 为什么需要日志映射?
code
日志映射的必要性:
│
├── 1. 问题排查
│ ├── 容器删除后日志丢失
│ ├── 需要保留历史日志
│ └── 便于回溯问题
│
├── 2. 日志分析
│ ├── 统一日志收集
│ ├── ELK 集成
│ └── 监控告警
│
├── 3. 合规要求
│ ├── 审计日志保留
│ ├── 安全合规
│ └── 数据备份
│
└── 4. 便捷查看
├── 无需进入容器
├── 宿主机直接查看
└── 使用熟悉的工具4.2 Volume 映射配置
Docker Compose 配置
yaml
version: '3.8'
services:
nest-app:
image: nest-app:1.0
container_name: nest-app-prod
restart: always
ports:
- "8000:3000"
env_file:
- .env
volumes:
# 日志映射:容器路径 → 宿主机路径
- ./logs:/home/node/app/logs
# 其他可能的映射
- ./uploads:/home/node/app/uploads
networks:
- app-network
networks:
app-network:
driver: bridgeNestJS 日志配置示例
typescript
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as fs from 'fs';
import * as path from 'path';
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
logger: ['error', 'warn', 'log'],
});
// 确保日志目录存在
const logDir = path.join(__dirname, '..', 'logs');
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true });
}
await app.listen(3000);
}
bootstrap();4.3 Windows 注意事项
问题:Windows 上相对路径映射可能失败
解决方案:
yaml
# 不推荐(Windows 可能失败)
volumes:
- ./logs:/home/node/app/logs
# 推荐(使用绝对路径)
volumes:
- /c/Users/YourName/project/logs:/home/node/app/logs
# 或 Windows 路径格式
- C:\Users\YourName\project\logs:/home/node/app/logs验证映射:
bash
# 进入容器查看
docker exec -it nest-app-prod sh
ls /home/node/app/logs
# 在宿主机查看
ls ./logs
# 两者应该内容一致五、完整部署流程
5.1 部署步骤
bash
# ==================== 第一步:构建镜像 ====================
docker build -t nest-app:1.0 .
# 查看构建结果
docker images | grep nest-app
# ==================== 第二步:运行容器(测试) ====================
docker run -d --name test -p 8000:3000 nest-app:1.0
# 查看运行状态
docker ps
# 查看日志
docker logs -f test
# ==================== 第三步:测试接口 ====================
# 访问健康检查接口
curl http://localhost:8000/api
# 预期错误(未配置数据库)
# PrismaClientInitializationError: Can't reach database server
# ==================== 第四步:配置环境变量 ====================
# 停止并删除测试容器
docker stop test
docker rm test
# 创建 .env 文件
cat > .env << EOF
DATABASE_URL="postgresql://postgres:password@postgres:5432/mydb"
JWT_SECRET="your-secret-key"
EOF
# ==================== 第五步:使用 Docker Compose 启动 ====================
docker compose up -d
# 查看运行状态
docker compose ps
# 查看日志
docker compose logs -f nest-app
# ==================== 第六步:验证服务 ====================
curl http://localhost:8000/api
# 预期输出:正常响应数据5.2 常用运维命令
bash
# ==================== 服务管理 ====================
# 启动服务
docker compose up -d
# 停止服务
docker compose down
# 重启服务
docker compose restart
# 重新构建并启动
docker compose up -d --build
# ==================== 日志查看 ====================
# 查看所有服务日志
docker compose logs
# 查看特定服务日志
docker compose logs nest-app
# 实时查看日志
docker compose logs -f nest-app
# 查看最后 100 行
docker compose logs --tail 100 nest-app
# ==================== 进入容器 ====================
# 进入应用容器
docker compose exec nest-app sh
# 进入数据库容器
docker compose exec postgres sh
# 以 root 用户进入
docker compose exec -u root nest-app sh
# ==================== 数据库操作 ====================
# 运行 Prisma 迁移
docker compose exec nest-app npx prisma migrate deploy
# 打开 Prisma Studio
docker compose exec nest-app npx prisma studio
# 查看数据库
docker compose exec postgres psql -U postgres -d mydb六、常见问题与解决方案
6.1 问题排查流程
code
NestJS 容器问题排查:
│
├── 1. 构建失败
│ ├── 检查 Dockerfile 语法
│ ├── 查看构建日志定位错误步骤
│ ├── 验证 .dockerignore 配置
│ └── 检查 prisma generate 是否执行
│
├── 2. 启动失败
│ ├── docker logs 查看容器日志
│ ├── 检查环境变量是否正确
│ ├── 验证数据库连接
│ └── 检查端口映射
│
├── 3. 数据库连接失败
│ ├── DATABASE_URL 配置错误
│ ├── 数据库服务未启动
│ ├── 网络不通(容器间通信)
│ └── 防火墙阻止
│
└── 4. Prisma 错误
├── Prisma Client 未生成
├── 未拷贝 .prisma 目录
└── Schema 文件缺失6.2 常见错误汇总
| 错误信息 | 原因分析 | 解决方案 |
|---|---|---|
PrismaClientInitializationError: Can't reach database server | DATABASE_URL 未配置或错误 | 配置正确的环境变量 |
Cannot find module '@prisma/client' | Prisma Client 未拷贝 | 在 Dockerfile 中添加 COPY 指令 |
Error: P1001: Can't reach database server | 数据库服务未运行 | 启动数据库容器 |
Permission denied | 文件权限问题 | 使用 --chown=node:node |
Port 3000 is already in use | 端口冲突 | 更换映射端口 |
ENOENT: no such file or directory | 文件路径错误 | 检查 COPY 和 WORKDIR |
6.3 数据库连接问题详解
问题:DATABASE_URL 配置
bash
# 错误示例(使用 localhost)
DATABASE_URL="postgresql://postgres:password@localhost:5432/mydb"
# 问题:
# - localhost 在容器内指向容器自身
# - 数据库在另一个容器中
# - 网络不通bash
# 正确示例(使用服务名)
DATABASE_URL="postgresql://postgres:password@postgres:5432/mydb"
# 说明:
# - postgres 是 docker-compose.yml 中定义的服务名
# - Docker Compose 自动创建 DNS 解析
# - 容器间通过服务名通信网络配置验证
bash
# 进入应用容器
docker compose exec nest-app sh
# 测试数据库连通性
ping postgres
# 使用 nc 测试端口
nc -zv postgres 5432
# 预期输出:Connection to postgres 5432 port [tcp/postgresql] succeeded!七、Docker Compose 多服务编排
7.1 完整生产级配置
yaml
version: '3.8'
services:
# PostgreSQL 数据库
postgres:
image: postgres:15-alpine
container_name: postgres-prod
restart: always
environment:
POSTGRES_USER: ${DB_USER:-postgres}
POSTGRES_PASSWORD: ${DB_PASSWORD:-password}
POSTGRES_DB: ${DB_NAME:-mydb}
ports:
- "5432:5432"
volumes:
- postgres-data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- app-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
# NestJS 应用
nest-app:
image: nest-app:1.0
container_name: nest-app-prod
restart: always
ports:
- "8000:3000"
env_file:
- .env
volumes:
- ./logs:/home/node/app/logs
- ./uploads:/home/node/app/uploads
depends_on:
postgres:
condition: service_healthy
networks:
- app-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# Nginx 反向代理(可选)
nginx:
image: nginx:alpine
container_name: nginx-proxy
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- nest-app
networks:
- app-network
volumes:
postgres-data:
driver: local
networks:
app-network:
driver: bridge7.2 depends_on 配置说明
yaml
# 方式一:简单依赖(不等待启动完成)
depends_on:
- postgres
# 方式二:条件依赖(等待健康检查通过)
depends_on:
postgres:
condition: service_healthy
# 说明:
# - service_healthy:等待健康检查通过
# - service_started:等待容器启动(默认)
# - service_completed_successfully:等待容器执行完成7.3 健康检查配置
yaml
# PostgreSQL 健康检查
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s # 检查间隔
timeout: 5s # 超时时间
retries: 5 # 重试次数
start_period: 10s # 启动等待时间
# NestJS 应用健康检查
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s # 给应用足够的启动时间八、生产环境最佳实践
8.1 安全配置清单
code
生产环境安全配置:
│
├── 1. 非 root 用户运行
│ ├── USER node
│ ├── WORKDIR /home/node/app
│ └── COPY --chown=node:node
│
├── 2. 环境变量安全
│ ├── .env 文件不入 Git
│ ├── 使用 Docker Secrets(Kubernetes)
│ └── 敏感信息加密存储
│
├── 3. 网络隔离
│ ├── 用户定义网络
│ ├── 不暴露不必要的端口
│ └── 数据库端口仅内网访问
│
├── 4. 镜像安全
│ ├── 使用官方基础镜像
│ ├── 定期更新基础镜像
│ ├── 镜像漏洞扫描
│ └── 使用特定版本标签(不用 latest)
│
└── 5. 日志管理
├── 日志映射到宿主机
├── 日志轮转配置
└── 统一日志收集系统8.2 性能优化
dockerfile
# ==================== 构建缓存优化 ====================
# 先复制依赖文件,利用 Docker 缓存
COPY package*.json ./
COPY prisma ./prisma/
RUN npm install
# 再复制源码(源码变化频繁)
COPY . .
RUN npm run build
# ==================== 层合并优化 ====================
# 合并多个 RUN 命令
RUN npm install && \
npm run build && \
npm cache clean --force
# ==================== 基础镜像选择 ====================
# 生产环境推荐
FROM node:18-alpine # 体积小(~170MB)
# 开发环境可用
FROM node:18-slim # 兼容性好(~240MB)
FROM node:18 # 功能全(~900MB)8.3 监控与告警
yaml
# docker-compose.yml 添加监控
services:
# 应用监控(可选)
prometheus:
image: prom/prometheus
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
grafana:
image: grafana/grafana
ports:
- "3001:3000"
volumes:
- grafana-data:/var/lib/grafana
volumes:
grafana-data:九、学习要点总结
核心要点
-
NestJS 与 Nuxt3 容器化的核心差异
- NestJS 需要手动安装生产依赖
- 必须拷贝 Prisma Client 文件
- 需要手动执行
prisma generate - 不像 Nuxt3 有
bundleDependencies
-
Prisma Client 是容器化的关键点
- 调整
package.json的preinstall脚本 - 构建阶段生成 client
- 生产阶段从 builder 拷贝
- 确保
.prisma和@prisma目录都拷贝
- 调整
-
数据库连接配置要点
- DATABASE_URL 使用服务名(不是 localhost)
- Docker Compose 自动 DNS 解析
- depends_on 等待数据库就绪
- 健康检查确保服务可用
-
日志映射提高运维效率
- Volume 映射日志目录
- 宿主机直接查看日志
- 便于问题排查和审计
- Windows 使用绝对路径
-
Docker Compose 简化多服务管理
- 一键启动完整环境
- 环境变量统一管理
- 服务依赖自动处理
- 网络自动配置
十、延伸学习资源
官方文档
进阶主题
code
推荐学习路径:
│
├── 1. CI/CD 自动化
│ ├── GitHub Actions
│ ├── 自动化测试
│ └── 自动化部署
│
├── 2. Kubernetes 部署
│ ├── Deployment 配置
│ ├── Service 配置
│ ├── Ingress 配置
│ └── ConfigMap 和 Secrets
│
├── 3. 数据库迁移
│ ├── Prisma Migrate
│ ├── 自动化迁移脚本
│ └── 多环境管理
│
├── 4. 监控与日志
│ ├── ELK Stack
│ ├── Prometheus + Grafana
│ └── 日志收集与分析
│
└── 5. 性能优化
├── 连接池配置
├── 缓存策略
└── 负载均衡实践建议
- 对比实践:部署 NestJS 和 Nuxt3 项目,对比配置差异
- 问题模拟:故意删除某个 COPY 指令,观察错误
- 日志分析:配置日志映射,模拟错误查看日志
- 多环境配置:创建 .env.development 和 .env.production
附录:命令速查表
Dockerfile 关键指令
dockerfile
# 多阶段构建
FROM node:18-alpine AS builder
FROM node:18-alpine AS production
# 安全配置
USER node
WORKDIR /home/node/app
COPY --chown=node:node . .
# Prisma 相关
COPY prisma ./prisma/
RUN npm run generate
COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma
COPY --from=builder /app/node_modules/@prisma ./node_modules/@prisma
# 生产依赖
RUN npm install --production
# 或
RUN npm install --omit=dev常用命令
bash
# 构建镜像
docker build -t nest-app:1.0 .
# 运行容器
docker run -d --name nest-app -p 8000:3000 -e DATABASE_URL="xxx" nest-app:1.0
# Docker Compose
docker compose up -d # 启动
docker compose down # 停止
docker compose logs -f # 查看日志
docker compose restart # 重启
docker compose ps # 查看状态
# 数据库操作
docker compose exec nest-app npx prisma migrate deploy # 运行迁移
docker compose exec nest-app npx prisma studio # 打开 Studio
# 调试命令
docker compose exec nest-app sh # 进入容器
docker compose exec postgres psql -U postgres # 进入数据库
docker logs -f nest-app # 查看日志环境变量模板
bash
# .env
DATABASE_URL="postgresql://postgres:password@postgres:5432/mydb"
JWT_SECRET="your-jwt-secret-key"
PORT=3000
NODE_ENV=production
# 可选
DB_USER=postgres
DB_PASSWORD=password
DB_NAME=mydb下一步学习:CI/CD 自动化部署、Kubernetes 生产部署、数据库迁移自动化、监控告警系统集成。