[NestJS] Deployment (Docker, Kubernetes, PM2)
정의
NestJS Deployment 는 프로덕션 환경에서 앱을 배포/운영하기 위한 절차입니다. Docker 이미지 빌드, Kubernetes 매니페스트, PM2 프로세스 관리, graceful shutdown, health check, 로깅, 관측 도구를 포함합니다.
Build
# 프로덕션 빌드
npm run build # nest build
# dist/ 에 컴파일된 JS
node dist/main.js
nest-cli.json:
{
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true,
"webpack": false // SWC (또는 esbuild) 옵션
}
}
SWC / esbuild (빠른 빌드)
npm i -D @swc/cli @swc/core
nest-cli.json:
{
"compilerOptions": {
"builder": "swc",
"typeCheck": true
}
}
주의: SWC 는 emitDecoratorMetadata 지원. class-validator 는 여전히 작동.
Dockerfile (multi-stage)
# --- builder ---
FROM node:20-alpine AS builder
WORKDIR /app
# Dependency cache
COPY package*.json ./
RUN npm ci
# Build
COPY . .
RUN npm run build
# Prisma (있으면)
RUN npx prisma generate
# 프로덕션 의존성만
RUN npm ci --omit=dev && npm cache clean --force
# --- runtime ---
FROM node:20-alpine
WORKDIR /app
# non-root user
RUN addgroup -g 1001 -S app && adduser -S app -u 1001
COPY --from=builder --chown=app:app /app/node_modules ./node_modules
COPY --from=builder --chown=app:app /app/dist ./dist
COPY --from=builder --chown=app:app /app/package.json ./
# Prisma
COPY --from=builder --chown=app:app /app/prisma ./prisma
USER app
EXPOSE 3000
# Graceful shutdown
CMD ["node", "dist/main"]
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=30s \
CMD wget --spider -q http://localhost:3000/health || exit 1
.dockerignore
node_modules
dist
.git
.env
.env.*
*.md
test
coverage
.vscode
.idea
Graceful Shutdown
// main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Shutdown hooks 활성화
app.enableShutdownHooks();
await app.listen(3000);
}
bootstrap();
Provider 에서 OnApplicationShutdown 구현:
@Injectable()
export class DatabaseService implements OnApplicationShutdown {
async onApplicationShutdown(signal?: string) {
console.log(`Shutting down: ${signal}`);
await this.prisma.$disconnect();
}
}
동작: SIGTERM 수신 -> 새 요청 accept 중지 -> in-flight 요청 완료 -> shutdown hook 실행 -> 프로세스 종료.
Health Check
@nestjs/terminus + @nestjs/health-check:
npm i @nestjs/terminus
// health.controller.ts
import { Controller, Get } from '@nestjs/common';
import { HealthCheck, HealthCheckService, HttpHealthIndicator, MemoryHealthIndicator } from '@nestjs/terminus';
import { PrismaHealthIndicator } from './prisma.health';
@Controller('health')
export class HealthController {
constructor(
private health: HealthCheckService,
private http: HttpHealthIndicator,
private memory: MemoryHealthIndicator,
private prisma: PrismaHealthIndicator,
) {}
@Get('live')
@HealthCheck()
liveness() {
return this.health.check([
() => this.memory.checkHeap('memory_heap', 250 * 1024 * 1024),
]);
}
@Get('ready')
@HealthCheck()
readiness() {
return this.health.check([
() => this.prisma.isHealthy('database'),
() => this.http.pingCheck('external_api', 'https://external-api/health'),
]);
}
}
Kubernetes probe
- livenessProbe: 앱 살아있는가 (fail 시 재시작)
- readinessProbe: 트래픽 받을 준비 (fail 시 LB 에서 제외)
- startupProbe: 초기 부팅 (긴 초기화)
Kubernetes Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 3
selector:
matchLabels: {app: myapp}
template:
metadata:
labels: {app: myapp}
spec:
terminationGracePeriodSeconds: 60
containers:
- name: app
image: myrepo/myapp:v1.2.3
ports: [{containerPort: 3000}]
env:
- name: NODE_ENV
value: production
- name: DATABASE_URL
valueFrom:
secretKeyRef: {name: myapp-secrets, key: db-url}
- name: JWT_SECRET
valueFrom:
secretKeyRef: {name: myapp-secrets, key: jwt-secret}
resources:
requests: {cpu: 250m, memory: 256Mi}
limits: {cpu: 1, memory: 1Gi}
livenessProbe:
httpGet: {path: /health/live, port: 3000}
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet: {path: /health/ready, port: 3000}
periodSeconds: 5
startupProbe:
httpGet: {path: /health/live, port: 3000}
failureThreshold: 30
periodSeconds: 5
lifecycle:
preStop:
exec:
command: ["sleep", "5"] # LB 제외 대기
---
apiVersion: v1
kind: Service
metadata:
name: myapp
spec:
selector: {app: myapp}
ports:
- port: 80
targetPort: 3000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts: [api.example.com]
secretName: myapp-tls
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service: {name: myapp, port: {number: 80}}
HPA
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: myapp-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp
minReplicas: 3
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
PM2 (Node 프로세스 관리자)
VM 배포 시 사용. Kubernetes 는 kubelet 이 그 역할.
npm i -g pm2
pm2 start dist/main.js --name myapp -i max # max = CPU 개수
pm2 status
pm2 logs myapp
pm2 reload myapp # zero-downtime reload
pm2 stop myapp
pm2 startup # systemd 등록
pm2 save # 재부팅 시 복원
ecosystem.config.js:
module.exports = {
apps: [{
name: 'myapp',
script: 'dist/main.js',
instances: 'max',
exec_mode: 'cluster',
env_production: {
NODE_ENV: 'production',
PORT: 3000,
},
max_memory_restart: '1G',
kill_timeout: 30000,
listen_timeout: 10000,
error_file: '/var/log/myapp/error.log',
out_file: '/var/log/myapp/out.log',
}],
};
로깅 (JSON, structured)
NestJS 11 native JSON logger
// main.ts
import { ConsoleLogger, LogLevel } from '@nestjs/common';
const app = await NestFactory.create(AppModule, {
logger: new ConsoleLogger({
json: true,
logLevels: ['error', 'warn', 'log'],
}),
});
nestjs-pino (권장)
npm i nestjs-pino pino-http
import { LoggerModule } from 'nestjs-pino';
@Module({
imports: [
LoggerModule.forRoot({
pinoHttp: {
level: process.env.LOG_LEVEL || 'info',
transport: process.env.NODE_ENV === 'development'
? { target: 'pino-pretty' }
: undefined,
redact: ['req.headers.authorization'],
},
}),
],
})
export class AppModule {}
main.ts:
const app = await NestFactory.create(AppModule, {bufferLogs: true});
app.useLogger(app.get(Logger));
OpenTelemetry
npm i @opentelemetry/api @opentelemetry/sdk-node \
@opentelemetry/auto-instrumentations-node \
@opentelemetry/exporter-trace-otlp-http
// tracing.ts
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
}),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
main.ts 최상단에 import './tracing'; (다른 import 보다 먼저).
Sentry
npm i @sentry/node
import * as Sentry from '@sentry/node';
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: 0.1,
});
// AllExceptionsFilter 안에서
Sentry.captureException(exception);
Database Migration
Prisma
# 배포 파이프라인에서
npx prisma migrate deploy
TypeORM
npm run typeorm migration:run
Kubernetes init container
initContainers:
- name: migrate
image: myrepo/myapp:v1.2.3
command: ['npx', 'prisma', 'migrate', 'deploy']
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef: {name: myapp-secrets, key: db-url}
containers:
- name: app
...
CI/CD 예시 (GitHub Actions)
name: deploy
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build & push
uses: docker/build-push-action@v5
with:
push: true
tags: |
ghcr.io/myorg/myapp:${{ github.sha }}
ghcr.io/myorg/myapp:latest
cache-from: type=gha
cache-to: type=gha,mode=max
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- uses: azure/setup-kubectl@v3
- uses: azure/k8s-set-context@v3
with:
method: kubeconfig
kubeconfig: ${{ secrets.KUBECONFIG }}
- run: |
kubectl set image deployment/myapp app=ghcr.io/myorg/myapp:${{ github.sha }}
kubectl rollout status deployment/myapp --timeout=5m
함정
WARNING
enableShutdownHooks() 안 부르면 SIGTERM 무시. docker stop 이 SIGKILL 로 강제 종료 -> in-flight 요청 손실.
CAUTION
PM2 는 이미 K8s 컨테이너 안에는 불필요. kubelet 이 프로세스 관리. PM2 는 VM 배포용.
WARNING
환경변수 secret 을 이미지에 굽지 마세요. 항상 runtime injection.
IMPORTANT
terminationGracePeriodSeconds > shutdown hook 완료 시간. K8s 가 강제 종료하기 전에 정리.
CAUTION
Prisma binary target. Alpine + linux-musl 명시 필요. 안 그러면 런타임 오류.
관련 위키
- NestJS - 상위 개요
- Config - env 관리
- Database - migration
- Microservices
- Testing - CI
- Modules - AppModule
- Kubernetes
- K8s Deployment
- K8s Service
- K8s Ingress
- Container Image Best Practices
- OCI Image
- FastAPI Deployment - 대비
- Flask Deployment - 대비
이 글의 용어 (14개)
- [Container] Image Best Practices: 작게, 안전하게virtualization
- 정의 컨테이너 image best practices = 작고 (small), 안전하고 (secure), 재현 가능하고 (reproducible), 서명된 (signed) imag…
- [Container] OCI Image: spec, manifest, layer 표준cloud
- 정의 OCI (Open Container Initiative) = 컨테이너 표준 (Linux Foundation, 2015). image format + runtime + dis…
- [FastAPI] Deployment (Uvicorn, Gunicorn, Docker)fastapi
- 정의 FastAPI 배포는 ASGI 서버 (Uvicorn) + 프로세스 관리자 (Gunicorn 또는 Uvicorn 자체) + 컨테이너 (Docker) + 오케스트레이터 (Kub…
- [Flask] Deployment (WSGI, Gunicorn, uWSGI, Docker)flask
- 정의 Flask 는 WSGI 앱. 프로덕션 배포는 WSGI 서버 (Gunicorn, uWSGI, gevent) + 리버스 프록시 (Nginx, ALB) + 컨테이너 (Docker…
- [Framework] NestJSframeworks
- 정의 NestJS 는 Kamil Myśliwiec 이 2017년 발표한 Node.js 서버측 프레임워크 입니다. TypeScript first, Angular 에서 영감받은 모듈…
- [K8s] Deployment: ReplicaSet, rolling update, rollbackkubernetes
- 정의 Deployment = stateless 워크로드를 위한 컨트롤러. 내부적으로 ReplicaSet 관리 + rolling update / rollback. 사용 시나리오 |…
- [K8s] Ingress: L7 라우팅, TLS termination, Gateway APIkubernetes
- 정의 Ingress = 클러스터 외부 HTTP/HTTPS 트래픽 → 내부 Service 라우팅. L7 LB 역할. Service 의 LoadBalancer 다수 대안. [!IMP…
- [K8s] Service: ClusterIP / NodePort / LoadBalancer / ExternalNamekubernetes
- 정의 Service = Pod 집합에 안정 가상 IP + DNS 부여. Pod 가 죽고 다시 만들어져도 Service IP 는 그대로. 4가지 타입 1. ClusterIP (기본…
- [NestJS] Config (Environment, Validation)frameworks
- 정의 NestJS Config 는 패키지로 환경변수를 관리합니다. 파일 로딩, TypeScript type-safe access, 검증 (Joi/Zod), 여러 환경 (dev/s…
- [NestJS] Database (TypeORM, Prisma, Mongoose, Drizzle)frameworks
- 정의 NestJS Database 통합은 여러 ORM/ODM 을 module 로 감싸 DI + 트랜잭션 + 마이그레이션을 관용화합니다. 주요 선택지: TypeORM (성숙), P…
- [NestJS] Microservices (Kafka, NATS, RabbitMQ, gRPC, TCP)frameworks
- 정의 NestJS Microservices 는 HTTP 대신 message broker (Kafka, RabbitMQ, NATS, Redis) 나 gRPC / TCP 로 통신하는…
- [NestJS] Modulesframeworks
- 정의 NestJS Module 은 데코레이터가 붙은 클래스로, 관련된 controller, provider, import/export 를 하나로 묶는 조직 단위입니다. Angul…
- [NestJS] Testing (Jest, Supertest, e2e)frameworks
- 정의 NestJS Testing 은 Jest (기본) + Supertest 조합으로 unit test 와 e2e test 를 지원합니다. 의 로 module 을 격리된 형태로 만…
- Kuberneteskubernetes
- 정의 Kubernetes (k8s) 는 컨테이너화된 애플리케이션의 배포, 스케일링, 관리 를 자동화하는 오픈소스 오케스트레이터입니다. Google 이 2014년 발표하고 2015…
💬 댓글