部署上线
本教程共 47 篇 · 第 46 篇 · 更新于 2026-08-09 · 约 15 分钟阅读
本节目标:掌握NestJS生产环境部署的完整流程,包括构建优化、进程管理、Docker容器化、Nginx反向代理、健康检查和CI/CD自动化部署。
代码写完了,测试也过了,该上线了。部署不是简单地把代码扔到服务器,要考虑构建优化、进程管理、容器化、负载均衡等一系列问题。
构建生产版本
部署的第一步是构建。把TypeScript编译成JavaScript:
npm run build
构建后的文件在dist目录:
dist/
├── main.js
├── app.module.js
├── users/
│ ├── users.controller.js
│ ├── users.service.js
│ └── ...
└── ...
启动应用:
node dist/main.js
Tip
nest start命令会自动先执行nest build,省去手动构建的步骤。
环境变量
生产环境的配置不要硬编码,用环境变量管理。
创建.env.production文件:
NODE_ENV=production
PORT=3000
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
JWT_SECRET=your-secret-key
JWT_EXPIRES_IN=1h
启动时指定环境:
NODE_ENV=production node dist/main.js
配置验证
用Joi验证环境变量,防止缺少必要配置:
import { ConfigModule } from '@nestjs/config';
import * as Joi from 'joi';
@Module({
imports: [
ConfigModule.forRoot({
validationSchema: Joi.object({
NODE_ENV: Joi.string()
.valid('development', 'production', 'test')
.default('development'),
PORT: Joi.number().default(3000),
DATABASE_URL: Joi.string().required(),
JWT_SECRET: Joi.string().required(),
}),
}),
],
})
export class AppModule {}
启动时如果缺少必要的环境变量,应用会直接报错退出,比运行时才发现要好得多。
PM2进程管理
直接node dist/main.js跑应用,进程挂了不会自动重启。PM2能解决这个问题。
安装PM2
npm install -g pm2
配置文件
创建ecosystem.config.js:
module.exports = {
apps: [
{
name: 'nest-app',
script: 'dist/main.js',
instances: 'max', // 根据CPU核心数启动多个实例
exec_mode: 'cluster', // 集群模式
autorestart: true, // 崩溃自动重启
watch: false, // 生产环境不要监听文件变化
max_memory_restart: '1G', // 内存超过1G自动重启
env: {
NODE_ENV: 'development',
PORT: 3000,
},
env_production: {
NODE_ENV: 'production',
PORT: 3000,
},
error_file: './logs/error.log',
out_file: './logs/out.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss',
},
],
};
instances: 'max'会根据CPU核心数启动多个进程,充分利用多核CPU。
常用命令
# 启动应用(生产环境)
pm2 start ecosystem.config.js --env production
# 查看状态
pm2 status
# 查看日志
pm2 logs
# 重启应用
pm2 restart nest-app
# 停止应用
pm2 stop nest-app
# 删除应用
pm2 delete nest-app
# 实时监控
pm2 monit
# 开机自启动
pm2 startup
pm2 save
Note
pm2 startup会生成开机自启动脚本,pm2 save保存当前进程列表。这样服务器重启后应用也会自动启动。
Docker容器化
Docker把应用和依赖打包在一起,到哪都能跑,不用担心环境问题。
Dockerfile
创建Dockerfile,用多阶段构建减小镜像体积:
# 构建阶段
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 生产阶段
FROM node:20-alpine AS production
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY --from=builder /app/dist ./dist
ENV NODE_ENV=production
ENV PORT=3000
EXPOSE 3000
USER node
CMD ["node", "dist/main.js"]
两个阶段:
- builder:安装所有依赖,编译TypeScript
- production:只安装生产依赖,复制编译后的文件
最终镜像只包含运行所需的文件,体积更小。
.dockerignore
创建.dockerignore,排除不需要的文件:
node_modules
dist
.git
.env
*.log
coverage
.idea
.vscode
构建和运行
# 构建镜像
docker build -t nest-app:latest .
# 运行容器
docker run -p 3000:3000 \
-e DATABASE_URL=postgresql://user:pass@host:5432/db \
nest-app:latest
Docker Compose
多个服务一起管理,用Docker Compose:
version: '3.8'
services:
app:
build:
context: .
target: production
ports:
- '3000:3000'
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://postgres:password@db:5432/mydb
- REDIS_URL=redis://redis:6379
depends_on:
- db
- redis
restart: unless-stopped
db:
image: postgres:14-alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: mydb
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- '80:80'
- '443:443'
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- app
restart: unless-stopped
volumes:
postgres_data:
redis_data:
启动所有服务:
docker-compose up -d
-d表示后台运行。
Nginx反向代理
NestJS应用前面放一层Nginx,处理SSL、静态文件、负载均衡。
Nginx配置
events {
worker_connections 1024;
}
http {
upstream nest_app {
server app:3000;
keepalive 64;
}
# HTTP重定向到HTTPS
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
# HTTPS服务
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
client_max_body_size 10M;
location / {
proxy_pass http://nest_app;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 60s;
proxy_connect_timeout 60s;
}
# 健康检查不记录日志
location /health {
proxy_pass http://nest_app/health;
access_log off;
}
}
# 开启gzip压缩
gzip on;
gzip_types text/plain text/css application/json application/javascript;
gzip_min_length 1000;
}
Nginx做了这些事:
- HTTP自动跳转HTTPS
- SSL证书管理
- 反向代理到NestJS应用
- 传递真实IP给后端
- gzip压缩响应
- WebSocket支持
健康检查
生产环境需要健康检查端点,让负载均衡器和监控系统知道应用是否正常。
安装@nestjs/terminus:
npm install @nestjs/terminus
创建健康检查控制器:
import { Controller, Get } from '@nestjs/common';
import {
HealthCheckService,
HealthCheck,
TypeOrmHealthIndicator,
MemoryHealthIndicator,
DiskHealthIndicator,
} from '@nestjs/terminus';
@Controller('health')
export class HealthController {
constructor(
private health: HealthCheckService,
private db: TypeOrmHealthIndicator,
private memory: MemoryHealthIndicator,
private disk: DiskHealthIndicator,
) {}
@Get()
@HealthCheck()
check() {
return this.health.check([
// 检查数据库连接
() => this.db.pingCheck('database'),
// 检查堆内存(不超过150MB)
() => this.memory.checkHeap('memory_heap', 150 * 1024 * 1024),
// 检查RSS内存(不超过150MB)
() => this.memory.checkRSS('memory_rss', 150 * 1024 * 1024),
// 检查磁盘空间(不超过90%)
() => this.disk.checkStorage('storage', {
thresholdPercent: 0.9,
path: '/',
}),
]);
}
}
访问/health会返回:
{
"status": "ok",
"info": {
"database": { "status": "up" },
"memory_heap": { "status": "up" },
"memory_rss": { "status": "up" },
"storage": { "status": "up" }
},
"error": {},
"details": { ... }
}
负载均衡器定期检查这个端点,如果返回非200状态码,就把流量切到其他实例。
优雅关闭
服务器收到SIGTERM信号时(比如部署新版本),应该先处理完当前请求再关闭,而不是直接杀掉进程。
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 监听SIGTERM信号
process.on('SIGTERM', async () => {
console.log('SIGTERM received, closing server...');
await app.close();
process.exit(0);
});
await app.listen(3000);
}
bootstrap();
app.close()会:
- 停止接收新请求
- 等待当前请求处理完成
- 关闭数据库连接
- 清理资源
TipDocker容器停止时会发送SIGTERM信号,给10秒宽限期。如果10秒内没退出,才发送SIGKILL强制杀掉。
CI/CD自动化部署
每次提交代码自动测试、构建、部署。以GitHub Actions为例:
name: Deploy
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm run test:cov
- name: Build
run: npm run build
- name: Build Docker image
run: docker build -t my-app:latest .
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Push Docker image
run: docker push my-app:latest
- name: Deploy to server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
script: |
cd /app
docker-compose pull
docker-compose up -d
docker image prune -f
流程:
- 拉取代码
- 安装依赖
- 跑测试
- 构建应用
- 构建Docker镜像
- 推送到Docker Hub
- SSH到服务器,拉取新镜像,重启容器
Warning敏感信息(密码、密钥)存在GitHub Secrets里,不要写在代码里。
日志管理
生产环境的日志很重要,方便排查问题。
自定义日志服务
用winston输出结构化日志:
import { LoggerService, Injectable } from '@nestjs/common';
import * as winston from 'winston';
@Injectable()
export class CustomLogger implements LoggerService {
private logger: winston.Logger;
constructor() {
this.logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json(),
),
transports: [
new winston.transports.Console(),
new winston.transports.File({
filename: 'logs/error.log',
level: 'error',
}),
new winston.transports.File({
filename: 'logs/combined.log',
}),
],
});
}
log(message: string) {
this.logger.info(message);
}
error(message: string, trace?: string) {
this.logger.error(message, { trace });
}
warn(message: string) {
this.logger.warn(message);
}
}
在main.ts里使用:
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
logger: new CustomLogger(),
});
await app.listen(3000);
}
bootstrap();
日志最佳实践
- 只记错误,不记异常:记录详细的错误信息,方便调试
- 不记敏感数据:密码、Token这些不要写进日志
- 用关联ID:分布式系统里,用唯一ID追踪请求链路
- 分级记录:info、warn、error分开,生产环境关掉debug
安全加固
启用Helmet
Helmet设置安全HTTP头:
import helmet from 'helmet';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(helmet());
await app.listen(3000);
}
bootstrap();
启用CORS
如果前端在不同域名,需要配置CORS:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableCors({
origin: 'https://example.com',
credentials: true,
});
await app.listen(3000);
}
bootstrap();
速率限制
防止接口被恶意刷:
import { ThrottlerModule } from '@nestjs/throttler';
@Module({
imports: [
ThrottlerModule.forRoot([{
ttl: 60000, // 60秒内
limit: 10, // 最多10次请求
}]),
],
})
export class AppModule {}
扩展策略
流量大了怎么办?两种扩展方式:
纵向扩展(Scale Up)
给服务器加资源:加CPU、加内存。简单粗暴,但有上限。
横向扩展(Scale Out)
加更多服务器实例,前面放负载均衡器分发流量。
┌─────────┐
│ Nginx │
│ 负载均衡 │
└────┬────┘
│
┌──────────┼──────────┐
│ │ │
┌───┴───┐ ┌───┴───┐ ┌───┴───┐
│ App 1 │ │ App 2 │ │ App 3 │
└───┬───┘ └───┬───┘ └───┬───┘
│ │ │
└──────────┼──────────┘
│
┌────┴────┐
│ 数据库 │
└─────────┘
NestJS是无状态的,天然支持横向扩展。只要Session存在Redis里,数据库共享,加多少实例都行。
Tip用PM2的cluster模式或Docker的多容器部署,都能实现横向扩展。配合Nginx或云厂商的负载均衡器,轻松应对高并发。
小结
这一章学了NestJS部署上线的完整流程:
构建:npm run build编译TypeScript,生成dist目录。
环境变量:用.env文件和Joi验证管理配置。
PM2:进程管理、自动重启、集群模式、开机自启。
Docker:多阶段构建减小镜像,Docker Compose管理多服务。
Nginx:反向代理、SSL、负载均衡、gzip压缩。
健康检查:用@nestjs/terminus检查数据库、内存、磁盘状态。
优雅关闭:处理完当前请求再退出,不丢请求。
CI/CD:GitHub Actions自动测试、构建、部署。
日志:用winston输出结构化日志,方便排查问题。
安全:Helmet、CORS、速率限制,基本的安全防护。
扩展:纵向扩展加资源,横向扩展加实例。
部署上线是把双刃剑。部署得好,用户无感知;部署得差,半夜被叫起来修Bug。把每个环节做好,上线就是水到渠成的事。最后一章我们聊聊性能和架构。