feat: [academy] PM2 프로세스 관리 가이드 페이지 추가

This commit is contained in:
김보곤
2026-02-24 08:00:25 +09:00
parent 66be684d40
commit 492e81c339

View File

@@ -0,0 +1,888 @@
@extends('layouts.app')
@section('title', 'PM2 프로세스 관리')
@push('styles')
<style>
.academy-img-hover {
transition: box-shadow 0.2s ease;
}
.academy-img-hover:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.help-balloon-trigger {
display: inline-flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
border-radius: 50%;
background: #eff6ff;
color: #1d4ed8;
font-size: 11px;
font-weight: 700;
cursor: pointer;
border: 1.5px solid #93c5fd;
vertical-align: middle;
margin-left: 4px;
transition: all 0.15s ease;
position: relative;
user-select: none;
}
.help-balloon-trigger:hover {
background: #1d4ed8;
color: white;
border-color: #3b82f6;
}
.help-balloon {
display: none;
position: absolute;
bottom: calc(100% + 10px);
left: 50%;
transform: translateX(-50%) scale(0.95);
background: #1e293b;
color: #f1f5f9;
font-size: 12px;
font-weight: 400;
line-height: 1.6;
padding: 10px 14px;
border-radius: 10px;
max-width: 300px;
min-width: 200px;
width: max-content;
white-space: normal;
word-break: keep-all;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
z-index: 40;
opacity: 0;
transition: opacity 0.15s ease, transform 0.15s ease;
pointer-events: none;
}
.help-balloon::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
border: 7px solid transparent;
border-top-color: #1e293b;
}
.help-balloon-trigger.is-active .help-balloon {
display: block;
opacity: 1;
transform: translateX(-50%) scale(1);
pointer-events: auto;
}
.code-block {
background: #1e1e2e;
color: #a6e3a1;
border-radius: 0.75rem;
padding: 1rem 1.25rem;
font-family: 'Courier New', Courier, monospace;
font-size: 0.75rem;
line-height: 1.7;
overflow-x: auto;
white-space: pre;
}
.code-block .code-comment { color: #6c7086; }
.code-block .code-keyword { color: #cba6f7; }
.code-block .code-string { color: #a6e3a1; }
.code-block .code-flag { color: #89b4fa; }
.code-block .code-path { color: #f9e2af; }
.code-block .code-output { color: #9399b2; }
.code-block .code-danger { color: #f38ba8; }
.code-block .code-prompt { color: #89dceb; }
.cmd-card {
transition: all 0.15s ease;
}
.cmd-card:hover {
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
}
</style>
@endpush
@section('content')
<div class="max-w-6xl mx-auto">
{{-- ============================================================ --}}
{{-- 히어로 배너 --}}
{{-- ============================================================ --}}
<div class="rounded-2xl overflow-hidden mb-8 shadow-lg" style="background: linear-gradient(135deg, #1a2a3a 0%, #0f172a 100%);">
<div class="flex items-center" style="flex-wrap: wrap;">
<div style="flex: 1 1 300px; padding: 2rem 2.5rem;">
<div class="flex items-center gap-2 text-sm mb-2" style="color: #93c5fd;">
<span>아카데미</span>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" /></svg>
<span style="color: #bfdbfe;">PM2 프로세스 관리</span>
</div>
<h1 class="text-3xl font-bold mb-2" style="color: #ffffff;">PM2 프로세스 관리</h1>
<p class="text-sm" style="color: #cbd5e1;">Node.js 앱을 24시간 안정적으로 유지하는 프로세스 매니저 비개발자를 위한 실전 가이드</p>
</div>
<div class="shrink-0" style="width: 240px; padding: 1.5rem;">
<div class="text-center" style="font-size: 100px; line-height: 1;">
<span style="filter: drop-shadow(0 4px 8px rgba(0,0,0,0.3));">&#9881;&#65039;</span>
</div>
</div>
</div>
</div>
<div class="flex gap-8">
{{-- ============================================================ --}}
{{-- 좌측 고정 목차 (TOC) --}}
{{-- ============================================================ --}}
<nav class="hidden lg:block shrink-0" style="width: 220px;">
<div class="sticky top-24">
<div class="bg-blue-50 border border-blue-200 rounded-xl p-5">
<h2 class="font-semibold text-blue-800 mb-3 flex items-center gap-2 text-sm">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" /></svg>
목차
</h2>
<div class="space-y-0.5 text-xs">
<a href="#what-is-pm2" class="block text-blue-700 hover:text-blue-900 py-1 font-medium">1. PM2란?</a>
<a href="#pm2-analogy" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">비유로 이해하기</a>
<a href="#pm2-3-problems" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">3가지 해결 포인트</a>
<a href="#pm2-sam-role" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">SAM에서의 역할</a>
<a href="#sam-pm2-structure" class="block text-blue-700 hover:text-blue-900 py-1 font-medium mt-2">2. SAM에서 PM2 사용 구조</a>
<a href="#structure-diagram" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">구조 다이어그램</a>
<a href="#structure-note" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">사용 환경</a>
<a href="#pm2-commands" class="block text-blue-700 hover:text-blue-900 py-1 font-medium mt-2">3. 자주 쓰는 PM2 명령어</a>
<a href="#cmd-list" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">pm2 list</a>
<a href="#cmd-start-restart" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">start / restart</a>
<a href="#cmd-stop-delete" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">stop / delete</a>
<a href="#cmd-logs-monit" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">logs / monit</a>
<a href="#cmd-save-startup" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">save / startup</a>
<a href="#sam-deploy" class="block text-blue-700 hover:text-blue-900 py-1 font-medium mt-2">4. SAM 배포와 PM2</a>
<a href="#deploy-flow" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">배포 흐름</a>
<a href="#deploy-why-local" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">로컬 빌드 이유</a>
<a href="#troubleshooting" class="block text-blue-700 hover:text-blue-900 py-1 font-medium mt-2">5. 트러블슈팅</a>
<a href="#trouble-not-found" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">앱을 찾을 </a>
<a href="#trouble-errored" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">errored 상태</a>
<a href="#trouble-reboot" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">재부팅 복구</a>
<a href="#comparison" class="block text-blue-700 hover:text-blue-900 py-1 font-medium mt-2">6. PM2 vs 다른 방법 비교</a>
</div>
</div>
</div>
</nav>
{{-- ============================================================ --}}
{{-- 우측 콘텐츠 --}}
{{-- ============================================================ --}}
<div class="flex-1 min-w-0 space-y-10">
{{-- 모바일 목차 --}}
<div class="lg:hidden bg-blue-50 border border-blue-200 rounded-xl p-4 mb-6">
<details>
<summary class="font-semibold text-blue-800 text-sm cursor-pointer flex items-center gap-2">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" /></svg>
목차 보기
</summary>
<nav class="mt-3 space-y-1 text-sm">
<a href="#what-is-pm2" class="block text-blue-700 hover:text-blue-900 py-1">1. PM2란? PM2를 쓰는가</a>
<a href="#sam-pm2-structure" class="block text-blue-700 hover:text-blue-900 py-1">2. SAM에서 PM2 사용 구조</a>
<a href="#pm2-commands" class="block text-blue-700 hover:text-blue-900 py-1">3. 자주 쓰는 PM2 명령어</a>
<a href="#sam-deploy" class="block text-blue-700 hover:text-blue-900 py-1">4. SAM 배포와 PM2</a>
<a href="#troubleshooting" class="block text-blue-700 hover:text-blue-900 py-1">5. 트러블슈팅</a>
<a href="#comparison" class="block text-blue-700 hover:text-blue-900 py-1">6. PM2 vs 다른 방법 비교</a>
</nav>
</details>
</div>
{{-- ============================================================ --}}
{{-- 1. PM2란? --}}
{{-- ============================================================ --}}
<section id="what-is-pm2" class="scroll-mt-20">
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
<span class="w-8 h-8 bg-blue-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">1</span>
PM2란? PM2를 쓰는가
</h2>
<div id="pm2-analogy" class="scroll-mt-20 mb-8">
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-blue-400 rounded-full"></span>
PM2 = Node.js 프로세스 매니저
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon"><strong>프로세스(Process)</strong> 현재 실행 중인 프로그램을 뜻한다. 크롬 브라우저를 열면 크롬 프로세스가 생기는 것처럼, Node.js 앱을 실행하면 Node.js 프로세스가 생긴다.</span></span>
</h3>
<div class="bg-blue-50 rounded-lg p-4 border border-blue-100 mb-4">
<p class="font-semibold text-blue-800 mb-2">비유: "프로그램이 꺼지면 자동으로 다시 켜주는 비서"</p>
<p class="text-xs text-blue-900 leading-relaxed">
회사에서 중요한 기계가 돌아가고 있다고 상상해보자. 기계가 갑자기 멈추면 누군가 와서 다시 켜줘야 한다.
PM2는 <strong>"누군가"</strong> 역할을 자동으로 해주는 프로그램이다.
기계(Node.js ) 멈추면 자동으로 다시 켜주고, 기계의 상태를 계속 감시해준다.
</p>
</div>
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100 mb-4">
<p class="font-semibold text-amber-800 mb-2">PM2 없이 실행하면 어떤 문제가?</p>
<p class="text-xs text-amber-900 leading-relaxed mb-2">
PM2 없이 <code class="bg-amber-100 px-1.5 py-0.5 rounded text-xs">npm start</code> 직접 실행하면 다음 문제가 발생한다:
</p>
<ul class="text-xs text-amber-800 space-y-1">
<li>1. <strong>터미널을 닫으면 앱이 꺼진다</strong> SSH 접속을 끊으면 실행 중이던 앱도 함께 종료된다.</li>
<li>2. <strong>에러가 나면 앱이 중단된다</strong> 예상치 못한 오류가 발생하면 앱이 멈추고, 누군가 수동으로 다시 켜야 한다.</li>
<li>3. <strong>상태 확인이 어렵다</strong> 앱이 돌고 있는지, CPU/메모리를 얼마나 쓰는지 알기 어렵다.</li>
</ul>
</div>
</div>
<div id="pm2-3-problems" class="scroll-mt-20 mb-8">
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-blue-400 rounded-full"></span>
PM2가 해결하는 3가지 문제
</h3>
<div class="grid gap-3" style="grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));">
<div class="bg-green-50 rounded-lg p-4 border border-green-200">
<div class="flex items-center gap-2 mb-2">
<span class="w-6 h-6 bg-green-500 text-white rounded-full flex items-center justify-center text-xs font-bold">1</span>
<p class="font-bold text-green-800 text-sm">자동 재시작</p>
</div>
<p class="text-xs text-green-700">앱이 에러로 멈추면(crash) PM2가 <strong>자동으로 다시 시작</strong>해준다. 새벽 3시에 에러가 나도 자동 복구된다.</p>
</div>
<div class="bg-green-50 rounded-lg p-4 border border-green-200">
<div class="flex items-center gap-2 mb-2">
<span class="w-6 h-6 bg-green-500 text-white rounded-full flex items-center justify-center text-xs font-bold">2</span>
<p class="font-bold text-green-800 text-sm">백그라운드 실행</p>
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon"><strong>백그라운드(Background)</strong> 화면에 보이지 않지만 뒤에서 계속 실행되는 것을 뜻한다. 스마트폰에서 음악 앱을 끄지 않고 다른 앱을 사용해도 음악이 계속 나오는 것과 같다.</span></span>
</div>
<p class="text-xs text-green-700">터미널(SSH) 닫아도 앱이 <strong>꺼지지 않고 계속 실행</strong>된다. 서버에 접속하지 않아도 24시간 유지된다.</p>
</div>
<div class="bg-green-50 rounded-lg p-4 border border-green-200">
<div class="flex items-center gap-2 mb-2">
<span class="w-6 h-6 bg-green-500 text-white rounded-full flex items-center justify-center text-xs font-bold">3</span>
<p class="font-bold text-green-800 text-sm">모니터링</p>
</div>
<p class="text-xs text-green-700">앱의 <strong>상태, CPU 사용량, 메모리 사용량, 로그</strong> 한눈에 확인할 있다. 문제가 생기면 빠르게 원인을 파악할 있다.</p>
</div>
</div>
</div>
<div id="pm2-sam-role" class="scroll-mt-20">
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-blue-400 rounded-full"></span>
SAM에서의 역할
</h3>
<div class="bg-indigo-50 rounded-lg p-4 border border-indigo-100">
<p class="text-xs text-indigo-900 leading-relaxed">
SAM 프로젝트에서 PM2는 <strong>SAM React (Next.js) 프론트엔드</strong> 운영 서버에서 24시간 유지하는 역할을 한다.
사용자가 SAM 웹사이트에 접속하면, PM2가 관리하는 Next.js 앱이 화면을 보여주는 것이다.
PM2가 없으면 서버 관리자가 매번 수동으로 앱을 켜고 감시해야 하지만, PM2 덕분에 과정이 <strong>완전히 자동화</strong>된다.
</p>
</div>
</div>
</div>
</section>
{{-- ============================================================ --}}
{{-- 2. SAM에서 PM2 사용 구조 --}}
{{-- ============================================================ --}}
<section id="sam-pm2-structure" class="scroll-mt-20">
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
<span class="w-8 h-8 bg-blue-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">2</span>
SAM에서 PM2 사용 구조
</h2>
<div id="structure-diagram" class="scroll-mt-20 mb-8">
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-blue-400 rounded-full"></span>
구조 다이어그램
</h3>
<p class="text-xs text-gray-600 mb-3">
운영 서버(114.203.209.83)에서 사용자의 요청이 처리되는 과정이다.
PM2는 Node.js(Next.js) 앱을 관리하며, Nginx가 사용자의 요청을 PM2가 관리하는 앱으로 전달한다.
</p>
<div class="code-block mb-4"><span class="code-comment"> 사용자 브라우저 (크롬, 사파리 )</span>
<span class="code-comment"> </span>
<span class="code-comment"> </span>
<span class="code-keyword"> ┌─────────────────────────────────────┐</span>
<span class="code-keyword"> </span> <span class="code-string">Nginx</span> (웹서버, 포트 <span class="code-flag">80</span>/<span class="code-flag">443</span>) <span class="code-keyword"></span>
<span class="code-keyword"> </span> - 사용자 요청을 받아서 전달하는 역할 <span class="code-keyword"></span>
<span class="code-keyword"> └────────────────┬────────────────────┘</span>
<span class="code-comment"> 프록시 (요청 전달)</span>
<span class="code-comment"> </span>
<span class="code-keyword"> ┌─────────────────────────────────────┐</span>
<span class="code-keyword"> </span> <span class="code-danger">PM2</span> <span class="code-string">Node.js</span> (Next.js, 포트 <span class="code-flag">3000</span>) <span class="code-keyword"></span>
<span class="code-keyword"> </span> - SAM 프론트엔드 화면 생성 <span class="code-keyword"></span>
<span class="code-keyword"> </span> - PM2가 앱을 감시/관리 <span class="code-keyword"></span>
<span class="code-keyword"> └────────────────┬────────────────────┘</span>
<span class="code-comment"> API 호출</span>
<span class="code-comment"> </span>
<span class="code-keyword"> ┌─────────────────────────────────────┐</span>
<span class="code-keyword"> </span> <span class="code-string">API 서버</span> (Laravel, PHP) <span class="code-keyword"></span>
<span class="code-keyword"> </span> - 데이터 처리 & DB 접근 <span class="code-keyword"></span>
<span class="code-keyword"> └─────────────────────────────────────┘</span></div>
<div class="bg-blue-50 rounded-lg p-3 border border-blue-100 text-xs text-blue-800">
<strong>요약:</strong> 사용자가 SAM에 접속하면 Nginx가 요청을 받고,
PM2가 관리하는 Next.js 앱이 화면을 생성하고, 필요한 데이터는 API 서버에서 가져온다.
</div>
</div>
<div id="structure-note" class="scroll-mt-20">
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-blue-400 rounded-full"></span>
PM2 사용 환경
</h3>
<div class="grid gap-3" style="grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));">
<div class="bg-green-50 rounded-lg p-4 border border-green-200">
<p class="font-bold text-green-800 mb-2">운영 서버 (114.203.209.83)</p>
<p class="text-xs text-green-700">PM2를 <strong>사용한다</strong>. 서버에 Node.js와 PM2가 직접 설치되어 있으며, <code class="bg-green-100 px-1 rounded">sam-react</code>라는 이름으로 Next.js 앱을 관리한다.</p>
</div>
<div class="bg-gray-50 rounded-lg p-4 border border-gray-200">
<p class="font-bold text-gray-800 mb-2">로컬 Docker 환경 ( PC)</p>
<p class="text-xs text-gray-600">PM2를 <strong>사용하지 않는다</strong>. 로컬에서는 Docker 컨테이너가 React 앱을 실행하므로 PM2가 필요 없다. <code class="bg-gray-100 px-1 rounded">npm run dev</code> 개발 서버를 실행한다.</p>
</div>
</div>
</div>
</div>
</section>
{{-- ============================================================ --}}
{{-- 3. 자주 쓰는 PM2 명령어 --}}
{{-- ============================================================ --}}
<section id="pm2-commands" class="scroll-mt-20">
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
<span class="w-8 h-8 bg-blue-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">3</span>
자주 쓰는 PM2 명령어
</h2>
<div class="bg-amber-50 rounded-lg p-3 border border-amber-100 text-xs text-amber-800 mb-6">
<strong>참고:</strong> 아래 명령어들은 모두 <strong>운영 서버</strong> SSH로 접속한 실행한다. 로컬( PC)에서는 사용하지 않는다.
</div>
{{-- pm2 list --}}
<div id="cmd-list" class="scroll-mt-20 mb-6">
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-blue-400 rounded-full"></span>
pm2 list 실행 중인 목록 확인
</h3>
<div class="border border-gray-200 rounded-lg p-4 cmd-card mb-3">
<p class="text-xs text-gray-600 mb-3">현재 PM2가 관리하고 있는 모든 앱의 상태를 형태로 보여준다. 이름, 상태(online/errored/stopped), CPU 사용량, 메모리 사용량을 한눈에 확인할 있다.</p>
<div class="code-block"><span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">list</span>
<span class="code-output">┌────┬──────────────┬─────────────┬─────────┬──────────┬───────────┐</span>
<span class="code-output"> id name mode status cpu memory </span>
<span class="code-output">├────┼──────────────┼─────────────┼─────────┼──────────┼───────────┤</span>
<span class="code-output"> 0 sam-react fork online 0.1% 120.5 MB </span>
<span class="code-output">└────┴──────────────┴─────────────┴─────────┴──────────┴───────────┘</span></div>
</div>
<div class="bg-blue-50 rounded-lg p-3 border border-blue-100 text-xs text-blue-800">
<strong>상태 의미:</strong>
<code class="bg-blue-100 px-1 rounded">online</code> = 정상 실행 ,
<code class="bg-red-100 px-1 rounded text-red-700">errored</code> = 에러 발생,
<code class="bg-gray-100 px-1 rounded text-gray-700">stopped</code> = 수동으로 중지됨
</div>
</div>
{{-- pm2 start / restart --}}
<div id="cmd-start-restart" class="scroll-mt-20 mb-6">
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-blue-400 rounded-full"></span>
pm2 start / pm2 restart 시작 & 재시작
</h3>
<div class="border border-gray-200 rounded-lg p-4 cmd-card mb-3">
<p class="text-xs text-gray-600 mb-3"><strong>start</strong> 새로운 앱을 PM2에 등록하고 실행한다. <strong>restart</strong> 이미 등록된 앱을 껐다가 다시 켠다.</p>
<div class="code-block"><span class="code-comment"># 새 앱 시작 (PM2에 처음 등록할 때)</span>
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">start</span> <span class="code-path">npm</span> <span class="code-flag">--name</span> <span class="code-string">sam-react</span> <span class="code-flag">--</span> <span class="code-string">start</span>
<span class="code-comment"># 해석: npm start 명령을 "sam-react"라는 이름으로 PM2에 등록하여 실행</span>
<span class="code-comment"># 이미 등록된 앱 재시작 (배포 후 주로 사용)</span>
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">restart</span> <span class="code-path">sam-react</span>
<span class="code-comment"># 해석: sam-react 앱을 껐다가 다시 켠다 (코드 변경 반영)</span></div>
</div>
<div class="bg-green-50 rounded-lg p-3 border border-green-100 text-xs text-green-800">
<strong>실무 :</strong> 배포 시에는 보통 <code class="bg-green-100 px-1 rounded">pm2 restart sam-react</code> 사용한다. <code>pm2 start</code> 최초 1회만 실행하면 된다.
</div>
</div>
{{-- pm2 stop / delete --}}
<div id="cmd-stop-delete" class="scroll-mt-20 mb-6">
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-blue-400 rounded-full"></span>
pm2 stop / pm2 delete 중지 & 삭제
</h3>
<div class="border border-gray-200 rounded-lg p-4 cmd-card mb-3">
<p class="text-xs text-gray-600 mb-3"><strong>stop</strong> 실행을 중지하지만 PM2 목록에는 남아있다. <strong>delete</strong> PM2 목록에서 완전히 제거한다.</p>
<div class="code-block"><span class="code-comment"># 앱 중지 (목록에는 남아있음, 나중에 pm2 start로 다시 실행 가능)</span>
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">stop</span> <span class="code-path">sam-react</span>
<span class="code-comment"># 앱 삭제 (PM2 목록에서 완전히 제거)</span>
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">delete</span> <span class="code-path">sam-react</span>
<span class="code-comment"># 삭제 후 다시 사용하려면 pm2 start로 새로 등록해야 한다</span></div>
</div>
<div class="bg-red-50 rounded-lg p-3 border border-red-200 text-xs text-red-800">
<strong>주의:</strong> <code class="bg-red-100 px-1 rounded">pm2 stop</code> 실행하면 <strong>SAM 프론트엔드가 접속 불가</strong>해진다. 유지보수 목적 외에는 실행하지 않는다.
</div>
</div>
{{-- pm2 logs / monit --}}
<div id="cmd-logs-monit" class="scroll-mt-20 mb-6">
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-blue-400 rounded-full"></span>
pm2 logs / pm2 monit 로그 & 모니터링
</h3>
<div class="border border-gray-200 rounded-lg p-4 cmd-card mb-3">
<p class="text-xs text-gray-600 mb-3"><strong>logs</strong> 앱의 실시간 출력(로그) 보여준다. 에러 원인 파악에 필수적이다. <strong>monit</strong> CPU, 메모리, 로그를 화면에서 실시간으로 있는 대시보드이다.</p>
<div class="code-block"><span class="code-comment"># 실시간 로그 보기 (Ctrl+C로 종료)</span>
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">logs</span> <span class="code-path">sam-react</span>
<span class="code-output">0|sam-react | Next.js 14.x</span>
<span class="code-output">0|sam-react | - Local: http://localhost:3000</span>
<span class="code-output">0|sam-react | Ready in 2.3s</span>
<span class="code-comment"># 최근 100줄만 보기</span>
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">logs</span> <span class="code-path">sam-react</span> <span class="code-flag">--lines 100</span>
<span class="code-comment"># 실시간 모니터링 대시보드 (Ctrl+C로 종료)</span>
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">monit</span>
<span class="code-comment"># CPU, 메모리, 로그를 한 화면에서 실시간 확인</span></div>
</div>
<div class="bg-green-50 rounded-lg p-3 border border-green-100 text-xs text-green-800">
<strong>실무 :</strong> 앱에 문제가 생기면 가장 먼저 <code class="bg-green-100 px-1 rounded">pm2 logs sam-react</code> 실행하여 에러 메시지를 확인한다.
</div>
</div>
{{-- pm2 save / startup --}}
<div id="cmd-save-startup" class="scroll-mt-20">
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-blue-400 rounded-full"></span>
pm2 save / pm2 startup 서버 재부팅 대비
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon"><strong>pm2 save</strong> 현재 PM2가 관리하는 목록을 파일로 저장한다. <strong>pm2 startup</strong> 서버가 부팅될 PM2를 자동으로 시작하도록 시스템에 등록한다. 명령을 함께 사용하면 서버 재부팅 후에도 앱이 자동으로 살아난다.</span></span>
</h3>
<div class="border border-gray-200 rounded-lg p-4 cmd-card mb-3">
<p class="text-xs text-gray-600 mb-3">서버가 재부팅되면 PM2도 함께 꺼진다. 재부팅 앱이 자동으로 살아나게 하려면 아래 명령을 실행해야 한다.</p>
<div class="code-block"><span class="code-comment"># 현재 프로세스 목록 저장 (재부팅 후 자동 복구용)</span>
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">save</span>
<span class="code-output">[PM2] Saving current process list...</span>
<span class="code-output">[PM2] Successfully saved in /home/pro/.pm2/dump.pm2</span>
<span class="code-comment"># 부팅 시 PM2 자동 시작 설정 (최초 1회만 실행)</span>
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">startup</span>
<span class="code-comment"># 시스템 서비스로 등록 — 서버 켜질 때 PM2가 자동 실행된다</span></div>
</div>
<div class="bg-amber-50 rounded-lg p-3 border border-amber-100 text-xs text-amber-800">
<strong>중요:</strong> PM2에 앱을 추가하거나 삭제한 후에는 반드시 <code class="bg-amber-100 px-1 rounded">pm2 save</code> 실행해야 한다. 그래야 서버 재부팅 후에도 변경된 목록이 유지된다.
</div>
</div>
{{-- 명령어 요약 --}}
<div class="mt-6 bg-gray-50 rounded-lg p-4 border border-gray-200">
<p class="font-bold text-gray-800 mb-3 text-sm">PM2 명령어 요약표</p>
<div class="overflow-x-auto">
<table class="w-full text-xs border-collapse">
<thead>
<tr class="bg-gray-100">
<th class="border border-gray-200 px-3 py-2 text-left">명령어</th>
<th class="border border-gray-200 px-3 py-2 text-left">설명</th>
<th class="border border-gray-200 px-3 py-2 text-left">사용 예시</th>
</tr>
</thead>
<tbody>
<tr>
<td class="border border-gray-200 px-3 py-2 font-mono font-bold">pm2 list</td>
<td class="border border-gray-200 px-3 py-2">실행 중인 목록 확인</td>
<td class="border border-gray-200 px-3 py-2"> 이름, 상태, CPU/메모리 표시</td>
</tr>
<tr class="bg-gray-50">
<td class="border border-gray-200 px-3 py-2 font-mono font-bold">pm2 start</td>
<td class="border border-gray-200 px-3 py-2"> 시작</td>
<td class="border border-gray-200 px-3 py-2 font-mono text-xs">pm2 start npm --name sam-react -- start</td>
</tr>
<tr>
<td class="border border-gray-200 px-3 py-2 font-mono font-bold">pm2 restart</td>
<td class="border border-gray-200 px-3 py-2"> 재시작</td>
<td class="border border-gray-200 px-3 py-2 font-mono text-xs">pm2 restart sam-react</td>
</tr>
<tr class="bg-gray-50">
<td class="border border-gray-200 px-3 py-2 font-mono font-bold">pm2 stop</td>
<td class="border border-gray-200 px-3 py-2"> 중지</td>
<td class="border border-gray-200 px-3 py-2 font-mono text-xs">pm2 stop sam-react</td>
</tr>
<tr>
<td class="border border-gray-200 px-3 py-2 font-mono font-bold">pm2 delete</td>
<td class="border border-gray-200 px-3 py-2"> 삭제 (목록에서 제거)</td>
<td class="border border-gray-200 px-3 py-2 font-mono text-xs">pm2 delete sam-react</td>
</tr>
<tr class="bg-gray-50">
<td class="border border-gray-200 px-3 py-2 font-mono font-bold">pm2 logs</td>
<td class="border border-gray-200 px-3 py-2">실시간 로그 보기</td>
<td class="border border-gray-200 px-3 py-2 font-mono text-xs">pm2 logs sam-react</td>
</tr>
<tr>
<td class="border border-gray-200 px-3 py-2 font-mono font-bold">pm2 monit</td>
<td class="border border-gray-200 px-3 py-2">실시간 모니터링 대시보드</td>
<td class="border border-gray-200 px-3 py-2">CPU, 메모리, 로그 실시간 확인</td>
</tr>
<tr class="bg-gray-50">
<td class="border border-gray-200 px-3 py-2 font-mono font-bold">pm2 save</td>
<td class="border border-gray-200 px-3 py-2">현재 프로세스 목록 저장</td>
<td class="border border-gray-200 px-3 py-2">서버 재부팅 자동 복구용</td>
</tr>
<tr>
<td class="border border-gray-200 px-3 py-2 font-mono font-bold">pm2 startup</td>
<td class="border border-gray-200 px-3 py-2">부팅 PM2 자동 시작 설정</td>
<td class="border border-gray-200 px-3 py-2">시스템 서비스로 등록</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</section>
{{-- ============================================================ --}}
{{-- 4. SAM 배포와 PM2 --}}
{{-- ============================================================ --}}
<section id="sam-deploy" class="scroll-mt-20">
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
<span class="w-8 h-8 bg-blue-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">4</span>
SAM 배포와 PM2
</h2>
<div id="deploy-flow" class="scroll-mt-20 mb-8">
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-blue-400 rounded-full"></span>
배포 흐름
</h3>
<p class="text-xs text-gray-600 mb-3">
SAM React 코드를 수정한 , 사용자에게 반영하기까지의 전체 흐름이다.
<code class="bg-gray-100 px-1.5 py-0.5 rounded text-xs">deploy.sh</code> 스크립트가 과정을 자동으로 처리한다.
</p>
<div class="code-block mb-4"><span class="code-comment"> 배포 흐름 (deploy.sh가 자동으로 처리)</span>
<span class="code-keyword"> 로컬에서 코드 수정 & git commit</span>
<span class="code-comment"> </span>
<span class="code-comment"> </span>
<span class="code-keyword"> ./deploy.sh 실행</span>
<span class="code-comment"> </span>
<span class="code-comment"> ├──▶</span> <span class="code-string"> git pull</span> <span class="code-comment">(최신 코드 받기)</span>
<span class="code-comment"> </span>
<span class="code-comment"> ├──▶</span> <span class="code-string"> npm run build</span> <span class="code-comment">(로컬에서 빌드)</span>
<span class="code-comment"> </span>
<span class="code-comment"> ├──▶</span> <span class="code-string"> .next 폴더 압축 서버 업로드</span>
<span class="code-comment"> </span>
<span class="code-comment"> ├──▶</span> <span class="code-string"> 서버에서 압축 해제</span>
<span class="code-comment"> </span>
<span class="code-comment"> └──▶</span> <span class="code-danger"> pm2 restart sam-react</span> <span class="code-comment">( 재시작!)</span>
<span class="code-comment"> 사용자가 화면을 있게 </span></div>
<div class="bg-blue-50 rounded-lg p-3 border border-blue-100 text-xs text-blue-800 mb-4">
<strong>핵심:</strong> deploy.sh 스크립트가 마지막 단계에서 자동으로 <code class="bg-blue-100 px-1 rounded">pm2 restart sam-react</code> 호출한다. 따라서 배포 수동으로 PM2 명령을 실행할 필요가 없다.
</div>
</div>
<div id="deploy-why-local" class="scroll-mt-20">
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-blue-400 rounded-full"></span>
서버에서 빌드하지 않는 이유
</h3>
<div class="bg-red-50 rounded-lg p-4 border border-red-200 mb-4">
<p class="font-semibold text-red-800 mb-2">서버 빌드 금지!</p>
<p class="text-xs text-red-700 leading-relaxed">
SAM 운영 서버의 스펙은 <strong>2코어 CPU, 3.8GB RAM</strong>이다.
Next.js 빌드(<code class="bg-red-100 px-1 rounded">npm run build</code>) 메모리를 많이 사용하는 작업으로,
스펙에서 실행하면 <strong>20 이상 소요</strong>되거나 <strong>메모리 부족으로 실패</strong>한다.
그래서 빌드는 로컬( PC)에서 하고, 빌드된 결과물(.next 폴더) 서버에 올리는 방식을 사용한다.
</p>
</div>
<div class="grid gap-3" style="grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));">
<div class="bg-green-50 rounded-lg p-4 border border-green-200">
<p class="font-bold text-green-800 mb-2">올바른 방법 (로컬 빌드)</p>
<div class="code-block" style="font-size: 11px;"><span class="code-comment"># 로컬(내 PC)에서</span>
<span class="code-prompt">$</span> <span class="code-keyword">npm run build</span>
<span class="code-comment"># 빌드 결과물을 서버로 전송</span>
<span class="code-comment"># PM2 재시작</span></div>
</div>
<div class="bg-red-50 rounded-lg p-4 border border-red-200">
<p class="font-bold text-red-800 mb-2">잘못된 방법 (서버 빌드)</p>
<div class="code-block" style="font-size: 11px;"><span class="code-comment"># 서버에서 직접 빌드 — 금지!</span>
<span class="code-danger">$ ssh 서버</span>
<span class="code-danger">$ npm run build</span>
<span class="code-comment"># → 메모리 부족, 서버 먹통 위험!</span></div>
</div>
</div>
</div>
</div>
</section>
{{-- ============================================================ --}}
{{-- 5. 트러블슈팅 --}}
{{-- ============================================================ --}}
<section id="troubleshooting" class="scroll-mt-20">
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
<span class="w-8 h-8 bg-blue-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">5</span>
트러블슈팅 "이럴 땐 어떻게?"
</h2>
{{-- 문제 1 --}}
<div id="trouble-not-found" class="scroll-mt-20 mb-6">
<div class="border border-red-200 rounded-lg p-4 cmd-card">
<div class="flex items-center gap-2 mb-2">
<span class="w-6 h-6 bg-red-500 text-white rounded-full flex items-center justify-center text-xs font-bold">1</span>
<p class="font-bold text-gray-800 text-sm">Process or Namespace sam-react not found</p>
</div>
<div class="code-block mb-3"><span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">restart</span> <span class="code-path">sam-react</span>
<span class="code-danger">[PM2][ERROR] Process or Namespace sam-react not found</span></div>
<div class="bg-amber-50 rounded-lg p-3 border border-amber-100 text-xs mb-3">
<p class="font-semibold text-amber-800 mb-1">원인:</p>
<p class="text-amber-700">PM2에 <code class="bg-amber-100 px-1 rounded">sam-react</code>라는 앱이 등록되어 있지 않다. PM2가 재설치되었거나, <code class="bg-amber-100 px-1 rounded">pm2 delete</code> 삭제된 경우 발생한다.</p>
</div>
<div class="bg-green-50 rounded-lg p-3 border border-green-200 text-xs">
<p class="font-semibold text-green-800 mb-1">해결:</p>
<div class="code-block" style="font-size: 11px;"><span class="code-comment"># Next.js 프로젝트 폴더로 이동</span>
<span class="code-prompt">$</span> <span class="code-keyword">cd</span> <span class="code-path">/home/webservice/react</span>
<span class="code-comment"># PM2에 앱을 새로 등록하고 시작</span>
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">start</span> <span class="code-path">npm</span> <span class="code-flag">--name</span> <span class="code-string">sam-react</span> <span class="code-flag">--</span> <span class="code-string">start</span>
<span class="code-comment"># 목록 저장 (재부팅 대비)</span>
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">save</span></div>
</div>
</div>
</div>
{{-- 문제 2 --}}
<div id="trouble-errored" class="scroll-mt-20 mb-6">
<div class="border border-red-200 rounded-lg p-4 cmd-card">
<div class="flex items-center gap-2 mb-2">
<span class="w-6 h-6 bg-red-500 text-white rounded-full flex items-center justify-center text-xs font-bold">2</span>
<p class="font-bold text-gray-800 text-sm">PM2 상태가 errored</p>
</div>
<div class="code-block mb-3"><span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">list</span>
<span class="code-output"> 0 sam-react fork </span> <span class="code-danger">errored</span> <span class="code-output"> 0% 0 B </span></div>
<div class="bg-amber-50 rounded-lg p-3 border border-amber-100 text-xs mb-3">
<p class="font-semibold text-amber-800 mb-1">원인:</p>
<p class="text-amber-700">Node.js (Next.js) 에러가 발생하여 실행이 중단된 상태이다. 코드 오류, 의존성 문제, 환경 변수 누락 등이 원인일 있다.</p>
</div>
<div class="bg-green-50 rounded-lg p-3 border border-green-200 text-xs">
<p class="font-semibold text-green-800 mb-1">해결:</p>
<div class="code-block" style="font-size: 11px;"><span class="code-comment"># 1. 먼저 에러 로그를 확인한다</span>
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">logs</span> <span class="code-path">sam-react</span> <span class="code-flag">--lines 50</span>
<span class="code-comment"># 2. 에러 원인을 파악한 후 수정</span>
<span class="code-comment"># 3. 앱 재시작</span>
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">restart</span> <span class="code-path">sam-react</span>
<span class="code-comment"># 4. 상태 확인 (online이면 정상)</span>
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">list</span></div>
</div>
</div>
</div>
{{-- 문제 3 --}}
<div id="trouble-reboot" class="scroll-mt-20 mb-6">
<div class="border border-red-200 rounded-lg p-4 cmd-card">
<div class="flex items-center gap-2 mb-2">
<span class="w-6 h-6 bg-red-500 text-white rounded-full flex items-center justify-center text-xs font-bold">3</span>
<p class="font-bold text-gray-800 text-sm">서버 재부팅 앱이 </p>
</div>
<div class="code-block mb-3"><span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">list</span>
<span class="code-output">┌────┬──────┬──────┬──────┬─────┬────────┐</span>
<span class="code-output"> id name mode ... ... ... </span>
<span class="code-output">└────┴──────┴──────┴──────┴─────┴────────┘</span>
<span class="code-comment">(목록이 비어있음)</span></div>
<div class="bg-amber-50 rounded-lg p-3 border border-amber-100 text-xs mb-3">
<p class="font-semibold text-amber-800 mb-1">원인:</p>
<p class="text-amber-700">이전에 <code class="bg-amber-100 px-1 rounded">pm2 save</code> 실행하지 않아서, PM2가 재부팅 어떤 앱을 실행해야 하는지 모르는 상태이다.</p>
</div>
<div class="bg-green-50 rounded-lg p-3 border border-green-200 text-xs">
<p class="font-semibold text-green-800 mb-1">해결:</p>
<div class="code-block" style="font-size: 11px;"><span class="code-comment"># 1. Next.js 프로젝트 폴더로 이동</span>
<span class="code-prompt">$</span> <span class="code-keyword">cd</span> <span class="code-path">/home/webservice/react</span>
<span class="code-comment"># 2. 앱 다시 시작</span>
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">start</span> <span class="code-path">npm</span> <span class="code-flag">--name</span> <span class="code-string">sam-react</span> <span class="code-flag">--</span> <span class="code-string">start</span>
<span class="code-comment"># 3. 반드시 저장! (다음 재부팅에 대비)</span>
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">save</span>
<span class="code-comment"># 4. (선택) 부팅 시 PM2 자동 시작 확인</span>
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">startup</span></div>
</div>
</div>
</div>
{{-- 문제 4 --}}
<div id="trouble-port" class="scroll-mt-20">
<div class="border border-red-200 rounded-lg p-4 cmd-card">
<div class="flex items-center gap-2 mb-2">
<span class="w-6 h-6 bg-red-500 text-white rounded-full flex items-center justify-center text-xs font-bold">4</span>
<p class="font-bold text-gray-800 text-sm">포트 3000 이미 사용 </p>
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon"><strong>포트(Port)</strong> 네트워크에서 프로그램을 식별하는 번호이다. 아파트 호수처럼, 서버 안에서 여러 프로그램이 각자 다른 포트 번호를 사용한다. Next.js는 기본적으로 포트 3000 사용한다.</span></span>
</div>
<div class="code-block mb-3"><span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">start</span> <span class="code-path">npm</span> <span class="code-flag">--name</span> <span class="code-string">sam-react</span> <span class="code-flag">--</span> <span class="code-string">start</span>
<span class="code-danger">Error: listen EADDRINUSE: address already in use :::3000</span></div>
<div class="bg-amber-50 rounded-lg p-3 border border-amber-100 text-xs mb-3">
<p class="font-semibold text-amber-800 mb-1">원인:</p>
<p class="text-amber-700">이전에 실행했던 Node.js 프로세스가 아직 포트 3000 점유하고 있다. PM2에서 제대로 종료되지 않은 잔여 프로세스가 남아있는 경우 발생한다.</p>
</div>
<div class="bg-green-50 rounded-lg p-3 border border-green-200 text-xs">
<p class="font-semibold text-green-800 mb-1">해결:</p>
<div class="code-block" style="font-size: 11px;"><span class="code-comment"># 1. 기존 앱을 PM2에서 완전히 제거</span>
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">delete</span> <span class="code-path">sam-react</span>
<span class="code-comment"># 2. 혹시 남아있는 Node.js 프로세스 확인</span>
<span class="code-prompt">$</span> <span class="code-keyword">ss</span> <span class="code-flag">-tulnp</span> | <span class="code-keyword">grep</span> <span class="code-string">:3000</span>
<span class="code-comment"># 3. 다시 시작</span>
<span class="code-prompt">$</span> <span class="code-keyword">cd</span> <span class="code-path">/home/webservice/react</span>
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">start</span> <span class="code-path">npm</span> <span class="code-flag">--name</span> <span class="code-string">sam-react</span> <span class="code-flag">--</span> <span class="code-string">start</span>
<span class="code-comment"># 4. 목록 저장</span>
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">save</span></div>
</div>
</div>
</div>
</div>
</section>
{{-- ============================================================ --}}
{{-- 6. PM2 vs 다른 방법 비교 --}}
{{-- ============================================================ --}}
<section id="comparison" class="scroll-mt-20">
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
<span class="w-8 h-8 bg-blue-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">6</span>
PM2 vs 다른 방법 비교
</h2>
<p class="text-xs text-gray-600 mb-4">
Node.js 앱을 실행하는 여러 방법을 비교한 표이다. PM2가 모든 면에서 우수하기 때문에 SAM에서 PM2를 선택했다.
</p>
<div class="overflow-x-auto mb-6">
<table class="w-full text-xs border-collapse">
<thead>
<tr class="bg-gray-100">
<th class="border border-gray-200 px-3 py-2 text-left">실행 방법</th>
<th class="border border-gray-200 px-3 py-2 text-center">백그라운드 실행</th>
<th class="border border-gray-200 px-3 py-2 text-center">자동 재시작</th>
<th class="border border-gray-200 px-3 py-2 text-center">모니터링</th>
<th class="border border-gray-200 px-3 py-2 text-center">로그 관리</th>
</tr>
</thead>
<tbody>
<tr>
<td class="border border-gray-200 px-3 py-2 font-mono font-bold">npm start</td>
<td class="border border-gray-200 px-3 py-2 text-center text-red-500 font-bold">X</td>
<td class="border border-gray-200 px-3 py-2 text-center text-red-500 font-bold">X</td>
<td class="border border-gray-200 px-3 py-2 text-center text-red-500 font-bold">X</td>
<td class="border border-gray-200 px-3 py-2 text-center text-red-500 font-bold">X</td>
</tr>
<tr class="bg-gray-50">
<td class="border border-gray-200 px-3 py-2 font-mono font-bold">nohup npm start &
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon"><strong>nohup</strong> 터미널을 닫아도 프로세스가 종료되지 않게 하는 명령어이다. <strong>&</strong> 백그라운드 실행을 의미한다.</span></span>
</td>
<td class="border border-gray-200 px-3 py-2 text-center text-green-500 font-bold">O</td>
<td class="border border-gray-200 px-3 py-2 text-center text-red-500 font-bold">X</td>
<td class="border border-gray-200 px-3 py-2 text-center text-red-500 font-bold">X</td>
<td class="border border-gray-200 px-3 py-2 text-center text-red-500 font-bold">X</td>
</tr>
<tr>
<td class="border border-gray-200 px-3 py-2 font-mono font-bold">screen / tmux
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon"><strong>screen</strong> <strong>tmux</strong> 터미널 세션을 유지해주는 도구이다. 터미널을 닫아도 세션이 유지되지만, 자동 재시작이나 모니터링 기능은 없다.</span></span>
</td>
<td class="border border-gray-200 px-3 py-2 text-center text-green-500 font-bold">O</td>
<td class="border border-gray-200 px-3 py-2 text-center text-red-500 font-bold">X</td>
<td class="border border-gray-200 px-3 py-2 text-center text-red-500 font-bold">X</td>
<td class="border border-gray-200 px-3 py-2 text-center text-red-500 font-bold">X</td>
</tr>
<tr class="bg-blue-50">
<td class="border border-blue-200 px-3 py-2 font-mono font-bold text-blue-800">PM2</td>
<td class="border border-blue-200 px-3 py-2 text-center text-green-600 font-bold">O</td>
<td class="border border-blue-200 px-3 py-2 text-center text-green-600 font-bold">O</td>
<td class="border border-blue-200 px-3 py-2 text-center text-green-600 font-bold">O</td>
<td class="border border-blue-200 px-3 py-2 text-center text-green-600 font-bold">O</td>
</tr>
</tbody>
</table>
</div>
<div class="bg-indigo-50 rounded-lg p-4 border border-indigo-100">
<p class="font-semibold text-indigo-800 mb-2">결론</p>
<p class="text-xs text-indigo-900 leading-relaxed">
<code class="bg-indigo-100 px-1 rounded">npm start</code> 개발 중에만 사용하는 방법이다.
실제 서비스를 운영할 때는 백그라운드 실행, 자동 재시작, 모니터링, 로그 관리를 모두 지원하는
<strong>PM2가 최적의 선택</strong>이다.
SAM에서도 이러한 이유로 PM2를 사용하여 Next.js 프론트엔드를 안정적으로 운영하고 있다.
</p>
</div>
</div>
</section>
</div>
</div>
</div>
{{-- Hover Preview --}}
<div id="hover-preview">
<img id="hover-preview-img" src="" alt="">
<div class="hover-caption" id="hover-preview-caption"></div>
</div>
{{-- Lightbox --}}
<div id="lightbox" onclick="closeLightbox()">
<img id="lightbox-img" src="" alt="">
</div>
@include('components.academy-glossary', ['domain' => 'pm2-guide'])
@endsection
@push('scripts')
<script>
(function() {
// 도움말 풍선 토글
window.toggleBalloon = function(el) {
var wasActive = el.classList.contains('is-active');
document.querySelectorAll('.help-balloon-trigger.is-active').forEach(function(t) {
t.classList.remove('is-active');
});
if (!wasActive) {
el.classList.add('is-active');
}
};
// 풍선 외부 클릭 시 닫기
document.addEventListener('click', function(e) {
if (!e.target.closest('.help-balloon-trigger')) {
document.querySelectorAll('.help-balloon-trigger.is-active').forEach(function(t) {
t.classList.remove('is-active');
});
}
});
// Lightbox
window.openLightbox = function(el) {
var lb = document.getElementById('lightbox');
var img = document.getElementById('lightbox-img');
img.src = el.src;
img.alt = el.alt;
lb.classList.add('is-open');
document.body.style.overflow = 'hidden';
};
window.closeLightbox = function() {
var lb = document.getElementById('lightbox');
lb.classList.remove('is-open');
document.body.style.overflow = '';
};
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeLightbox();
}
});
})();
</script>
@endpush