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