Node.js 교재 · 25편 · 배포

배포 — PM2와 Docker로 production

node server.js 만으론 production 못 간다. 자동 재시작·무중단·확장이 필요한 시점.

PM2 와 Docker 가 Node 앱을 production 에 배포하는 컨셉 일러스트

로컬에서 node server.js 잘 돌면 끝일 것 같지만 — production 은 그것만으론 부족하다. 서버가 죽으면? 메모리 leak 누적? 새 코드 배포 시 다운타임? 8 코어 활용? 직접 다 해결하려면 일주일.

표준 답은 두 가지 — PM2(전통적 VM/베어메탈 환경) 또는 Docker(컨테이너 오케스트레이션). 둘 다 배워두면 어떤 회사 환경이든 적응 가능. 이번이 마지막 인프라 챕터.

1. PM2 — 프로세스 매니저 표준

"node server.js 의 production 버전"

$ npm install -g pm2 # 기본 실행 $ pm2 start server.js --name api # cluster 모드 (23편 + 모든 CPU 코어) $ pm2 start server.js --name api -i max # 환경변수 + watch $ pm2 start server.js -i max --env production

이 명령 하나가 23편 cluster 모듈을 자동 적용 + 죽으면 재시작 + 로그 통합. 명령 5개로 일상 운영.

명령역할
pm2 list실행 중 프로세스 + CPU·메모리
pm2 logs api실시간 로그 (tail)
pm2 reload api무중단 재배포 (워커 순차 교체)
pm2 stop api / delete멈춤·완전 삭제
pm2 startup + save서버 부팅 시 자동 시작 등록

pm2 reload 가 진짜 강력 — 워커 1개씩 새 코드로 교체. 다운타임 0. 결제·API 처럼 절대 끊기면 안 되는 서비스에 필수.

2. ecosystem.config.js — 설정 한 파일로

옵션이 많아지면 명령보다 파일.

// ecosystem.config.cjs module.exports = { apps: [{ name: 'api', script: 'server.js', instances: 'max', // 모든 CPU exec_mode: 'cluster', env: { NODE_ENV: 'production', PORT: 3000, }, max_memory_restart: '500M', // 메모리 leak 보호 error_file: '/var/log/api-err.log', out_file: '/var/log/api-out.log', log_date_format: 'YYYY-MM-DD HH:mm:ss', }], }; // 실행 // $ pm2 start ecosystem.config.cjs

max_memory_restart 가 자주 쓰임 — 메모리가 임계점 넘으면 자동 재시작. 잠재적 leak 대응.

3. Docker — 컨테이너 표준

PM2 가 "내 서버 위에서 잘 돌게" 라면, Docker 는 "어디서나 똑같이 돌게". 의존성 + Node 버전 + OS 라이브러리까지 한 이미지로.

# Dockerfile (multi-stage, 권장) FROM node:22-alpine AS deps WORKDIR /app COPY package*.json ./ RUN npm ci --omit=dev FROM node:22-alpine AS runtime WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . ENV NODE_ENV=production ENV PORT=3000 EXPOSE 3000 USER node # ★ root 로 실행 금지 CMD ["node", "server.js"]

핵심 두 가지 — multi-stage 빌드(빌드 캐시 + 최종 이미지 크기 감소) + USER node(보안, root 실행 금지).

$ docker build -t my-api . $ docker run -p 3000:3000 --env-file .env.production my-api

.dockerignore 필수

# .dockerignore node_modules .git .env .env.local *.log coverage .next .vscode

없으면 node_modules + .git 까지 이미지에 포함되어 수백 MB. .gitignore 와 거의 같이.

4. docker-compose — 멀티 컨테이너 묶음

API + DB + Redis 같은 묶음을 한 파일로.

# docker-compose.yml services: api: build: . ports: ['3000:3000'] env_file: .env.production depends_on: [db, redis] restart: unless-stopped db: image: postgres:17-alpine environment: POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_DB: myapp volumes: - dbdata:/var/lib/postgresql/data restart: unless-stopped redis: image: redis:7-alpine restart: unless-stopped volumes: dbdata:
$ docker compose up -d # 백그라운드 시작 $ docker compose logs -f api # 로그 $ docker compose down # 정지

로컬 개발도 production 도 같은 파일. "내 컴에선 되는데요" 가 거의 사라진다.

5. 12-factor 체크리스트

production 코드 배포 전 점검. 12-factor app 의 핵심 5개만 추려보면:

원칙체크
설정은 환경변수17편 dotenv·Zod 검증
로그는 stdout21편 winston Console transport
상태 없는 프로세스세션은 DB·Redis, 파일은 S3
graceful shutdownSIGTERM 처리, in-flight 요청 끝까지
한 단계 한 명령npm start 한 줄로 시작

graceful shutdown — 자주 빠뜨림

// server.js const server = app.listen(3000); const shutdown = (signal) => { console.log(`${signal} 수신, graceful 종료 시작`); server.close(() => { console.log('HTTP 서버 종료'); db.end(); // DB 풀 닫기 process.exit(0); }); // 10초 안에 안 끝나면 강제 종료 setTimeout(() => process.exit(1), 10000); }; process.on('SIGTERM', shutdown); process.on('SIGINT', shutdown);

없으면 PM2·Docker 재시작 시 진행 중이던 요청이 통째로 끊김. 결제 같은 곳에선 사고.

오늘날 현실 — Kubernetes·서버리스도 알아둘 것 — 작은 회사·사이드는 PM2 + 1 서버, 중규모는 Docker + docker-compose 또는 ECS/Cloud Run, 대규모는 Kubernetes(EKS/GKE)·Vercel·Cloudflare Workers. PM2·Docker 가 그 위로 가는 발판. 이 둘만 단단해도 신입 백엔드 인터뷰 90% 통과.

요약 — 25편 좌표

여기까지 정리. PM2 — 단일 서버에서 cluster + 자동 재시작 + 무중단 재배포 (pm2 reload) + 자동 부팅. Docker — multi-stage + USER node + .dockerignore. docker-compose 로 API+DB+Redis 묶음. graceful shutdown(SIGTERM) 빠뜨리지 말 것. 12-factor 5개 체크리스트가 production 준비도 측정. 다음 편은 시리즈 마지막 — 실전 Todo API 미니 프로젝트로 25편 전체 합치기.

다음 편 예고 — 실전 미니 프로젝트 (완결)

Todo API 처음부터 끝까지 — 25편 모든 개념 결합. 26편.

© 2026 주나이테크(주) @JUNAITECH