출시·고도화 중
NestJS · Express 안내서 · 6/6
Nest 애플리케이션은 TypeScript를 JavaScript로 빌드한 뒤 Node.js로 dist/main.js를 실행하는 평범한 Node.js 서버입니다. 이 장에서는 운영용 빌드, 환경 설정, 프로세스 관리자와 컨테이너를 이용한 배포를 살펴봅니다. Express 앱도 대부분 같은 방식으로 배포합니다.
npm run build는 nest build를 실행해 src를 dist 폴더로 컴파일합니다. 빌드한 결과는 Nest CLI 없이 Node.js만으로 실행합니다.
npm ci
npm run build
NODE_ENV=production node dist/main
# 또는 생성된 스크립트
npm run start:prod운영 서버에서 개발 의존성은 필요 없으므로, 빌드를 마친 뒤 npm prune --omit=dev로 지우거나 npm ci --omit=dev로 운영 의존성만 설치합니다. ts-node나 nest start로 운영 서버를 돌리는 것은 피합니다. Express 앱은 컴파일 단계가 없다면 node src/server.js처럼 바로 실행합니다.
설정은 코드와 분리해 환경 변수로 넣습니다. 포트는 PORT로 받고, 데이터베이스 주소와 비밀 값은 배포 플랫폼의 비밀 저장소나 환경 변수로 전달합니다. .env 파일을 이미지나 저장소에 넣지 않습니다. 운영용 main.ts에는 보통 다음과 같은 설정이 더해집니다.
// src/main.ts
import { ValidationPipe } from "@nestjs/common";
import { NestFactory } from "@nestjs/core";
import helmet from "helmet";
import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(helmet()); // 보안 관련 HTTP 헤더
app.enableCors({ origin: process.env.CORS_ORIGIN });
app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
app.enableShutdownHooks(); // SIGTERM을 받으면 정리 후 종료
await app.listen(process.env.PORT ?? 3000, "0.0.0.0");
}
bootstrap();helmet은 npm i helmet으로 설치하는 Express 미들웨어로, Nest에서도 app.use()로 그대로 씁니다. enableShutdownHooks()를 켜면 컨테이너가 종료 신호를 보낼 때 onModuleDestroy 같은 수명 주기 훅이 실행되어 데이터베이스 연결을 깨끗하게 닫을 수 있습니다. 리버스 프록시 뒤에서 클라이언트 IP나 HTTPS 여부를 정확히 읽으려면 Express의 trust proxy 설정을 켭니다(Nest에서는 NestFactory.create<NestExpressApplication>()으로 만든 앱에서 app.set("trust proxy", 1)).
가상 머신이나 서버에 직접 배포할 때는 프로세스가 죽으면 다시 띄워 주는 관리자가 필요합니다. PM2는 Node.js에서 널리 쓰이는 프로세스 관리자로, 클러스터 모드로 CPU 코어 수만큼 프로세스를 띄울 수도 있습니다.
npm i -g pm2
pm2 start dist/main.js --name api -i max
pm2 logs api
pm2 save # 현재 프로세스 목록 저장
pm2 startup # 서버가 재부팅되면 자동 시작Linux의 systemd 서비스로 node dist/main.js를 등록하는 방법도 많이 씁니다. 앞에는 Nginx 같은 리버스 프록시를 두어 TLS와 정적 파일을 맡깁니다.
컨테이너를 쓰면 같은 이미지를 어디서나 실행할 수 있습니다. 빌드 단계와 실행 단계를 나눈 다단계 Dockerfile로 이미지를 작게 만듭니다.
FROM node:lts-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build && npm prune --omit=dev
FROM node:lts-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/package*.json ./
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
USER node
EXPOSE 3000
CMD ["node", "dist/main.js"].dockerignore에 node_modules, dist, .env, .git를 넣어 빌드 맥락을 가볍게 하고 비밀 값이 이미지에 들어가지 않게 합니다. 컨테이너 안에서는 PM2 없이 node를 직접 실행하고, 재시작과 복제는 Docker, Kubernetes, 클라우드 컨테이너 서비스에 맡기는 것이 일반적입니다.
docker build -t my-api .
docker run -p 3000:3000 -e DATABASE_URL="postgres://user:pass@db:5432/app" my-apinpm run build와 테스트가 CI에서 통과하는지 확인합니다.ConfigModule.forRoot의 validate 옵션 등).synchronize는 끕니다.@nestjs/terminus 제공).npm run build로 dist를 만들고 node dist/main으로 실행하며, 운영에는 운영 의존성만 둡니다.main.ts에 더합니다.
댓글 0개
로그인 · 로그인하면 댓글을 남길 수 있습니다.
첫 댓글을 남겨 보세요.