首页 / NestJS 入门教程 / 部署上线

NestJS 入门教程

部署上线

本教程共 47 篇 · 第 46 篇 · 更新于 2026-08-09 · 约 15 分钟阅读

NestJS部署DockerPM2NginxCI/CD

本节目标:掌握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()会:

  • 停止接收新请求
  • 等待当前请求处理完成
  • 关闭数据库连接
  • 清理资源
Tip

Docker容器停止时会发送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

流程:

  1. 拉取代码
  2. 安装依赖
  3. 跑测试
  4. 构建应用
  5. 构建Docker镜像
  6. 推送到Docker Hub
  7. 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。把每个环节做好,上线就是水到渠成的事。最后一章我们聊聊性能和架构。