[2026] Node.js 배포 가이드 | PM2, Docker, AWS, Nginx
이 글의 핵심
Node.js 배포 가이드: PM2, Docker, AWS, Nginx. PM2 (Process Manager)·Docker.
들어가며
배포 체크리스트
로컬에서 node app.js로 돌리던 것을 프로세스 관리(PM2)·리버스 프록시(Nginx)·컨테이너(Docker)까지 올리면, 재시작·로그·환경 분리·무중단 배포 같은 운영 요구를 맞출 수 있습니다. 아래 체크리스트는 “코드만 올리는 것”이 아니라 실행 환경까지 포함했을 때의 최소 점검 항목입니다.
배포 전에는 Node.js 테스트(Jest 등)로 회귀를 막고, GitHub Actions CI/CD로 빌드·테스트를 자동화하는 흐름이 흔합니다. 로컬·스테이징은 Docker Compose, 오케스트레이션 입문은 minikube, C++·네이티브 쪽은 C++ Docker·배포 이미지·C++ GitHub Actions와 같은 언어 무관 패턴을 비교해 보세요.
배포 전 확인사항:
- 환경 변수 설정
- 프로덕션 의존성만 설치
- 에러 로깅 설정
- 보안 설정 (Helmet, CORS)
- 데이터베이스 마이그레이션
- 정적 파일 빌드
- 테스트 통과
- 성능 테스트 배포 방식:
- ✅ 전통적 방식: VPS, PM2, Nginx
- ✅ 컨테이너: Docker, Kubernetes
- ✅ 서버리스: AWS Lambda, Vercel
- ✅ PaaS: Heroku, Railway, Render
실무에서 마주한 현실
개발을 배울 때는 모든 게 깔끔하고 이론적입니다. 하지만 실무는 다릅니다. 레거시 코드와 씨름하고, 급한 일정에 쫓기고, 예상치 못한 버그와 마주합니다. 이 글에서 다루는 내용도 처음엔 이론으로 배웠지만, 실제 프로젝트에 적용하면서 “아, 이래서 이렇게 설계하는구나” 하고 깨달은 것들입니다. 특히 기억에 남는 건 첫 프로젝트에서 겪은 시행착오입니다. 책에서 배운 대로 했는데 왜 안 되는지 몰라 며칠을 헤맸죠. 결국 선배 개발자의 코드 리뷰를 통해 문제를 발견했고, 그 과정에서 많은 걸 배웠습니다. 이 글에서는 이론뿐 아니라 실전에서 마주칠 수 있는 함정들과 해결 방법을 함께 다루겠습니다.
1. PM2 (Process Manager)
설치
# 전역 설치
npm install -g pm2
기본 사용법
아래 코드는 bash를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# 앱 시작
pm2 start app.js
# 이름 지정
pm2 start app.js --name "my-app"
# 환경 변수 설정
pm2 start app.js --name "my-app" --env production
# Watch 모드 (파일 변경 시 재시작)
pm2 start app.js --watch
# 인터프리터 지정
pm2 start app.js --interpreter node
클러스터 모드
아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
# 클러스터 모드 (멀티 코어 활용)
pm2 start app.js -i max # CPU 코어 수만큼
# 특정 개수
pm2 start app.js -i 4
# 무중단 재시작
pm2 reload my-app
PM2 명령어
다음은 bash를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# 상태 확인
pm2 status
pm2 list
# 로그 확인
pm2 logs
pm2 logs my-app
pm2 logs --lines 100
# 모니터링
pm2 monit
# 재시작
pm2 restart my-app
pm2 restart all
# 중지
pm2 stop my-app
pm2 stop all
# 삭제
pm2 delete my-app
pm2 delete all
# 정보
pm2 info my-app
# 저장 (현재 프로세스 목록)
pm2 save
# 부팅 시 자동 시작
pm2 startup
pm2 save
ecosystem.config.js
다음은 javascript를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ecosystem.config.js
module.exports = {
apps: [{
name: 'my-app',
script: './app.js',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'development',
PORT: 3000
},
env_production: {
NODE_ENV: 'production',
PORT: 8080
},
error_file: './logs/err.log',
out_file: './logs/out.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss',
merge_logs: true,
max_memory_restart: '500M',
watch: false,
ignore_watch: ['node_modules', 'logs'],
max_restarts: 10,
min_uptime: '10s'
}]
};
사용: 아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
# 시작
pm2 start ecosystem.config.js
# 프로덕션 환경
pm2 start ecosystem.config.js --env production
# 재시작
pm2 restart ecosystem.config.js
2. Docker
Dockerfile
다음은 dockerfile를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# Dockerfile
FROM node:20-alpine
# 작업 디렉토리
WORKDIR /app
# 의존성 파일 복사
COPY package*.json ./
# 의존성 설치
RUN npm ci --only=production
# 소스 코드 복사
COPY . .
# 포트 노출
EXPOSE 3000
# 헬스체크
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node healthcheck.js
# 앱 시작
CMD ["node", "app.js"]
.dockerignore
아래 코드는 code를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
node_modules
npm-debug.log
.env
.git
.gitignore
README.md
.vscode
coverage
.DS_Store
Docker 명령어
다음은 bash를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# 이미지 빌드
docker build -t my-app:1.0.0 .
# 컨테이너 실행
docker run -d \
--name my-app \
-p 3000:3000 \
-e NODE_ENV=production \
-e PORT=3000 \
my-app:1.0.0
# 로그 확인
docker logs my-app
docker logs -f my-app # 실시간
# 컨테이너 중지/시작
docker stop my-app
docker start my-app
# 컨테이너 재시작
docker restart my-app
# 컨테이너 삭제
docker rm my-app
# 이미지 삭제
docker rmi my-app:1.0.0
Docker Compose
다음은 yaml를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- PORT=3000
- MONGODB_URI=mongodb://mongo:27017/mydb
depends_on:
- mongo
restart: unless-stopped
volumes:
- ./logs:/app/logs
mongo:
image: mongo:7
ports:
- "27017:27017"
volumes:
- mongo-data:/data/db
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
depends_on:
- app
restart: unless-stopped
volumes:
mongo-data:
실행: 아래 코드는 bash를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# 시작
docker-compose up -d
# 로그
docker-compose logs -f
# 중지
docker-compose down
# 재시작
docker-compose restart
# 빌드 후 시작
docker-compose up -d --build
3. Nginx 리버스 프록시
설치 (Ubuntu)
sudo apt update
sudo apt install nginx
기본 설정
다음은 nginx를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# /etc/nginx/sites-available/myapp
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
location / {
proxy_pass http://localhost:3000;
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;
}
}
활성화:
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
SSL (Let’s Encrypt)
아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
# Certbot 설치
sudo apt install certbot python3-certbot-nginx
# SSL 인증서 발급
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
# 자동 갱신 테스트
sudo certbot renew --dry-run
SSL 설정: 다음은 nginx를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
server {
listen 443 ssl http2;
server_name yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://localhost:3000;
# ....프록시 설정
}
}
# HTTP → HTTPS 리다이렉트
server {
listen 80;
server_name yourdomain.com;
return 301 https://$server_name$request_uri;
}
로드 밸런싱
다음은 nginx를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# upstream 정의
upstream backend {
least_conn; # 연결 수가 적은 서버로
server localhost:3000;
server localhost:3001;
server localhost:3002;
}
server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://backend;
# ....프록시 설정
}
}
4. AWS 배포
EC2 배포
1. EC2 인스턴스 생성:
- Ubuntu Server 선택
- 보안 그룹: HTTP(80), HTTPS(443), SSH(22) 포트 열기 2. 서버 설정: 다음은 bash를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# SSH 접속
ssh -i your-key.pem ubuntu@your-ec2-ip
# Node.js 설치
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
# Git 설치
sudo apt-get install git
# 프로젝트 클론
git clone https://github.com/yourusername/your-repo.git
cd your-repo
# 의존성 설치
npm ci --only=production
# 환경 변수 설정
nano .env
# PM2로 실행
npm install -g pm2
pm2 start app.js --name "my-app" -i max
pm2 startup
pm2 save
# Nginx 설정
sudo apt install nginx
sudo nano /etc/nginx/sites-available/myapp
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Elastic Beanstalk
설치:
pip install awsebcli
초기화: 다음은 bash를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
eb init
# 환경 생성 및 배포
eb create production
eb deploy
# 로그 확인
eb logs
# 환경 변수 설정
eb setenv NODE_ENV=production PORT=8080
# 상태 확인
eb status
# 종료
eb terminate production
Lambda (서버리스)
아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// lambda.js
const serverless = require('serverless-http');
const app = require('./app');
module.exports.handler = serverless(app);
serverless.yml: 다음은 yaml를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
service: my-app
provider:
name: aws
runtime: nodejs20.x
region: ap-northeast-2
functions:
app:
handler: lambda.handler
events:
- http:
path: /{proxy+}
method: ANY
cors: true
배포:
npm install -g serverless
serverless deploy
5. 환경 변수 관리
.env 파일
아래 코드는 bash를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# .env.development
NODE_ENV=development
PORT=3000
MONGODB_URI=mongodb://localhost:27017/mydb-dev
JWT_SECRET=dev-secret
# .env.production
NODE_ENV=production
PORT=8080
MONGODB_URI=mongodb://prod-server:27017/mydb
JWT_SECRET=super-secret-production-key
dotenv 사용
다음은 javascript를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// config.js
require('dotenv').config({
path: `.env.${process.env.NODE_ENV || 'development'}`
});
const config = {
nodeEnv: process.env.NODE_ENV || 'development',
port: process.env.PORT || 3000,
mongodbUri: process.env.MONGODB_URI,
jwtSecret: process.env.JWT_SECRET,
isDevelopment: process.env.NODE_ENV === 'development',
isProduction: process.env.NODE_ENV === 'production',
isTest: process.env.NODE_ENV === 'test'
};
// 필수 환경 변수 검증
const required = ['MONGODB_URI', 'JWT_SECRET'];
for (const key of required) {
if (!process.env[key]) {
throw new Error(`환경 변수 ${key}가 필요합니다`);
}
}
module.exports = config;
AWS Systems Manager Parameter Store
아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
# AWS CLI로 환경 변수 저장
aws ssm put-parameter \
--name "/myapp/production/JWT_SECRET" \
--value "your-secret-key" \
--type "SecureString"
다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// config/aws.js
const AWS = require('aws-sdk');
const ssm = new AWS.SSM({ region: 'ap-northeast-2' });
async function loadConfig() {
const params = {
Names: [
'/myapp/production/JWT_SECRET',
'/myapp/production/MONGODB_URI'
],
WithDecryption: true
};
const result = await ssm.getParameters(params).promise();
const config = {};
result.Parameters.forEach(param => {
const key = param.Name.split('/').pop();
config[key] = param.Value;
});
return config;
}
module.exports = { loadConfig };
6. 로깅
Winston
npm install winston
다음은 javascript를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// logger.js
const winston = require('winston');
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: { service: 'my-app' },
transports: [
// 파일 로그
new winston.transports.File({
filename: 'logs/error.log',
level: 'error'
}),
new winston.transports.File({
filename: 'logs/combined.log'
})
]
});
// 개발 환경에서는 콘솔 출력
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
}));
}
module.exports = logger;
아래 코드는 javascript를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// app.js
const logger = require('./logger');
logger.info('서버 시작', { port: 3000 });
logger.error('에러 발생', { error: err.message, stack: err.stack });
logger.warn('경고', { memory: process.memoryUsage() });
Morgan (HTTP 로깅)
다음은 javascript를 활용한 상세한 구현 코드입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const morgan = require('morgan');
const logger = require('./logger');
// 커스텀 스트림
const stream = {
write: (message) => {
logger.info(message.trim());
}
};
// 프로덕션
if (process.env.NODE_ENV === 'production') {
app.use(morgan('combined', { stream }));
} else {
app.use(morgan('dev'));
}
7. 모니터링
PM2 모니터링
아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
# 실시간 모니터링
pm2 monit
# 웹 대시보드
pm2 plus
헬스체크
다음은 javascript를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// healthcheck.js
const http = require('http');
const options = {
host: 'localhost',
port: 3000,
path: '/health',
timeout: 2000
};
const request = http.request(options, (res) => {
if (res.statusCode === 200) {
process.exit(0);
} else {
process.exit(1);
}
});
request.on('error', () => {
process.exit(1);
});
request.end();
아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// app.js
// 실행 예제
app.get('/health', (req, res) => {
res.status(200).json({
status: 'ok',
uptime: process.uptime(),
timestamp: Date.now()
});
});
메트릭 수집
npm install prom-client
다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const promClient = require('prom-client');
// 기본 메트릭 수집
const register = new promClient.Registry();
promClient.collectDefaultMetrics({ register });
// 커스텀 메트릭
const httpRequestDuration = new promClient.Histogram({
name: 'http_request_duration_seconds',
help: 'HTTP 요청 처리 시간',
labelNames: ['method', 'route', 'status_code'],
registers: [register]
});
// 미들웨어
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = (Date.now() - start) / 1000;
httpRequestDuration
.labels(req.method, req.route?.path || req.path, res.statusCode)
.observe(duration);
});
next();
});
// 메트릭 엔드포인트
app.get('/metrics', async (req, res) => {
res.set('Content-Type', register.contentType);
res.end(await register.metrics());
});
8. CI/CD
GitHub Actions
다음은 yaml를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Run linter
run: npm run lint
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Deploy to EC2
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.EC2_HOST }}
username: ubuntu
key: ${{ secrets.EC2_SSH_KEY }}
script: |
cd /home/ubuntu/my-app
git pull origin main
npm ci --only=production
pm2 reload ecosystem.config.js --env production
Docker 이미지 빌드 및 배포
다음은 yaml를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# .github/workflows/docker.yml
name: Build and Deploy Docker
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: yourusername/my-app:latest
- name: Deploy to server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_KEY }}
script: |
docker pull yourusername/my-app:latest
docker stop my-app || true
docker rm my-app || true
docker run -d \
--name my-app \
-p 3000:3000 \
-e NODE_ENV=production \
yourusername/my-app:latest
9. 무중단 배포
PM2 무중단 재시작
아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
# reload: 무중단 재시작 (클러스터 모드)
pm2 reload my-app
# gracefulReload: 더 안전한 재시작
pm2 gracefulReload my-app
Graceful Shutdown
다음은 javascript를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// app.js
const express = require('express');
const app = express();
const server = app.listen(3000);
// 진행 중인 요청 추적
let connections = new Set();
server.on('connection', (conn) => {
connections.add(conn);
conn.on('close', () => {
connections.delete(conn);
});
});
// Graceful Shutdown
function gracefulShutdown(signal) {
console.log(`${signal} 신호 받음. 서버 종료 중...`);
// 새 연결 거부
server.close(async () => {
console.log('서버 종료됨');
// 데이터베이스 연결 종료
await mongoose.connection.close();
process.exit(0);
});
// 30초 후 강제 종료
setTimeout(() => {
console.error('강제 종료');
process.exit(1);
}, 30000);
// 기존 연결 종료
connections.forEach((conn) => {
conn.end();
setTimeout(() => {
conn.destroy();
}, 5000);
});
}
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
10. 자주 발생하는 문제
문제 1: 포트 충돌
에러:
Error: listen EADDRINUSE: address already in use :::3000
해결: 아래 코드는 bash를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# 프로세스 찾기
lsof -i :3000
netstat -ano | findstr :3000
# 프로세스 종료
kill -9 <PID>
taskkill /PID <PID> /F
# PM2로 관리
pm2 delete all
pm2 start app.js
문제 2: 메모리 누수
증상: 메모리 사용량이 계속 증가 해결: 다음은 javascript를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// PM2 설정
module.exports = {
apps: [{
name: 'my-app',
script: './app.js',
max_memory_restart: '500M' // 500MB 초과 시 재시작
}]
};
// 메모리 모니터링
setInterval(() => {
const used = process.memoryUsage();
console.log({
rss: `${Math.round(used.rss / 1024 / 1024)} MB`,
heapTotal: `${Math.round(used.heapTotal / 1024 / 1024)} MB`,
heapUsed: `${Math.round(used.heapUsed / 1024 / 1024)} MB`
});
}, 60000);
문제 3: 환경 변수 누락
아래 코드는 javascript를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 시작 시 검증
const required = [
'NODE_ENV',
'PORT',
'MONGODB_URI',
'JWT_SECRET'
];
for (const key of required) {
if (!process.env[key]) {
console.error(`환경 변수 ${key}가 설정되지 않았습니다`);
process.exit(1);
}
}
11. 실전 팁
배포 스크립트
아래 코드는 json를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
{
"scripts": {
"start": "node app.js",
"dev": "nodemon app.js",
"test": "jest",
"lint": "eslint .",
"build": "npm ci --only=production",
"deploy": "npm run test && npm run build && pm2 reload ecosystem.config.js"
}
}
롤백 전략
아래 코드는 bash를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# Git 태그로 버전 관리
git tag v1.0.0
git push origin v1.0.0
# 배포
git checkout v1.0.0
npm ci --only=production
pm2 reload my-app
# 롤백
git checkout v0.9.9
npm ci --only=production
pm2 reload my-app
블루-그린 배포
아래 코드는 nginx를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# Nginx 설정
upstream backend {
server localhost:3000; # Blue (현재)
}
# 배포 시:
# 1. Green 환경에 새 버전 배포 (포트 3001)
# 2. 테스트
# 3. Nginx 설정 변경
upstream backend {
server localhost:3001; # Green (새 버전)
}
# 4. Nginx 리로드
# 5. Blue 환경 종료
정리
핵심 요약
- PM2: 프로세스 관리, 클러스터 모드, 자동 재시작
- Docker: 컨테이너화, 환경 일관성
- Nginx: 리버스 프록시, SSL, 로드 밸런싱
- AWS: EC2, Elastic Beanstalk, Lambda
- CI/CD: GitHub Actions, 자동 배포
- 모니터링: 로그, 메트릭, 헬스체크
배포 방식 비교
| 방식 | 장점 | 단점 | 사용 사례 |
|---|---|---|---|
| VPS + PM2 | 완전한 제어 | 관리 부담 | 중소규모 |
| Docker | 환경 일관성 | 학습 곡선 | 마이크로서비스 |
| PaaS | 간편함 | 제한적 | 빠른 프로토타입 |
| 서버리스 | 자동 확장 | Cold Start | 이벤트 기반 |
배포 체크리스트
코드:
- 테스트 통과
- Linter 통과
- 프로덕션 빌드
- 의존성 최신화 설정:
- 환경 변수 설정
- 데이터베이스 마이그레이션
- SSL 인증서
- 방화벽 규칙 모니터링:
- 로깅 설정
- 에러 추적
- 성능 모니터링
- 알림 설정 보안:
- 비밀 키 관리
- HTTPS 적용
- Rate Limiting
- 보안 헤더
다음 단계
- Node.js 성능 최적화
- Node.js 보안 심화
- Node.js 마이크로서비스
추천 학습 자료
도구: