NestJS部署上线 #
部署概述 #
将NestJS应用部署到生产环境需要考虑构建优化、进程管理、容器化、负载均衡等多个方面。
构建生产版本 #
构建命令 #
bash
npm run build
构建输出 #
构建后的文件位于dist目录:
text
dist/
├── main.js
├── app.module.js
├── users/
│ ├── users.controller.js
│ ├── users.service.js
│ └── ...
└── ...
生产启动 #
bash
node dist/main.js
环境配置 #
环境变量 #
env
NODE_ENV=production
PORT=3000
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
JWT_SECRET=your-secret-key
JWT_EXPIRES_IN=1h
配置验证 #
typescript
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进程管理 #
安装PM2 #
bash
npm install -g pm2
PM2配置文件 #
ecosystem.config.js:
javascript
module.exports = {
apps: [
{
name: 'nest-app',
script: 'dist/main.js',
instances: 'max',
exec_mode: 'cluster',
autorestart: true,
watch: false,
max_memory_restart: '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',
},
],
};
PM2命令 #
bash
# 启动应用
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
Docker容器化 #
Dockerfile #
dockerfile
# 构建阶段
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 生产阶段
FROM node:18-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"]
.dockerignore #
text
node_modules
dist
.git
.env
*.log
coverage
.idea
.vscode
Docker Compose #
docker-compose.yml:
yaml
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命令 #
bash
# 构建镜像
docker build -t nest-app:latest .
# 运行容器
docker run -p 3000:3000 nest-app:latest
# 使用Docker Compose
docker-compose up -d
# 查看日志
docker-compose logs -f app
# 停止服务
docker-compose down
Nginx反向代理 #
Nginx配置 #
nginx.conf:
nginx
events {
worker_connections 1024;
}
http {
upstream nest_app {
server app:3000;
keepalive 64;
}
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
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 on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
gzip_min_length 1000;
}
CI/CD部署 #
GitHub Actions #
.github/workflows/deploy.yml:
yaml
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: '18'
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 ${{ secrets.DOCKER_IMAGE }}: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 ${{ secrets.DOCKER_IMAGE }}: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
云平台部署 #
AWS部署 #
Elastic Beanstalk #
yaml
# .elasticbeanstalk/config.yml
branch-defaults:
main:
environment: nest-app-prod
global:
application_name: nest-app
default_ec2_keyname: aws-key
default_platform: Node.js 18 running on 64bit Amazon Linux 2
default_region: us-east-1
profile: eb-cli
sc: git
ECS Fargate #
task-definition.json:
json
{
"family": "nest-app",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "256",
"memory": "512",
"containerDefinitions": [
{
"name": "nest-app",
"image": "your-ecr-repo/nest-app:latest",
"essential": true,
"portMappings": [
{
"containerPort": 3000,
"protocol": "tcp"
}
],
"environment": [
{ "name": "NODE_ENV", "value": "production" }
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/nest-app",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "ecs"
}
}
}
]
}
阿里云部署 #
容器服务Kubernetes #
deployment.yaml:
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nest-app
spec:
replicas: 3
selector:
matchLabels:
app: nest-app
template:
metadata:
labels:
app: nest-app
spec:
containers:
- name: nest-app
image: registry.cn-hangzhou.aliyuncs.com/your-namespace/nest-app:latest
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: production
resources:
requests:
cpu: '100m'
memory: '128Mi'
limits:
cpu: '500m'
memory: '512Mi'
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
健康检查 #
健康检查端点 #
typescript
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'),
() => this.memory.checkHeap('memory_heap', 150 * 1024 * 1024),
() => this.memory.checkRSS('memory_rss', 150 * 1024 * 1024),
() => this.disk.checkStorage('storage', { thresholdPercent: 0.9, path: '/' }),
]);
}
}
监控与日志 #
日志配置 #
typescript
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);
}
}
最佳实践 #
1. 使用环境变量 #
不要硬编码配置,使用环境变量。
2. 启用压缩 #
typescript
import compression from 'compression';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(compression());
await app.listen(3000);
}
3. 启用安全头 #
typescript
import helmet from '@nestjs/platform-express';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(helmet());
await app.listen(3000);
}
4. 优雅关闭 #
typescript
async function bootstrap() {
const app = await NestFactory.create(AppModule);
process.on('SIGTERM', async () => {
console.log('SIGTERM received, closing server...');
await app.close();
process.exit(0);
});
await app.listen(3000);
}
总结 #
本章学习了NestJS部署上线:
- 构建生产版本
- PM2进程管理
- Docker容器化
- Nginx反向代理
- CI/CD部署
- 云平台部署
- 健康检查
- 监控与日志
恭喜你完成了NestJS完全指南的学习!
最后更新:2026-03-28