[RESEARCH-2026-W33] MCP 서버 배포 방식 비교 2026 — Docker·systemd·PM2 어떤 게 안정적일까

MCP 서버를 Docker·systemd·PM2 중 무엇으로 배포해야 안정적일까. 재시작 정책·격리 수준·무중단 재배포 기준으로 세 가지 방식을 실제 설정 예시와 함께 비교했다. Node.js 서버라면 PM2, 이미 Docker 인프라가 있다면 컨테이너, 단순한 개인 서버라면 systemd — 상황별 선택 기준까지 정리.

MCP 서버 배포는 Docker·systemd·PM2 중 무엇이 가장 안정적일까? 정답은 상황에 따라 다르지만, 재시작 정책과 격리 수준을 기준으로 보면 단일 호스트 운영은 systemd, 다중 서비스 격리는 Docker, Node.js 기반 프로세스 관리는 PM2가 우세하다.

MCP(Model Context Protocol) 서버를 로컬 stdio 모드로 띄워서 Claude Desktop 하나에만 연결해 쓰던 단계를 지나면, 다음 질문이 반드시 나온다. “이 프로세스가 죽으면 누가 다시 살리는가?” 개발 환경에서는 터미널을 열어두고 python server.py를 직접 실행하면 그만이지만, 사내 여러 팀이 같은 MCP 서버를 Streamable HTTP로 공유하는 순간부터는 이야기가 달라진다. 서버 재부팅, 메모리 누수, 배포 중 다운타임 같은 문제를 누군가는 처리해야 한다.

이 글은 Kez9의 MCP 서버 만드는 법 2026에서 다룬 빌드 단계 이후, 실제로 그 서버를 24시간 안정적으로 돌리는 배포 단계에 집중한다. Docker, systemd, PM2 세 가지 방식을 구조적 차이·설정 난도·운영 리스크 기준으로 비교하고, 각 방식이 실제로 어떤 상황에 맞는지 정리했다.

결론부터 말하면 세 도구는 경쟁 관계가 아니라 계층이 다르다. systemd는 OS 레벨 프로세스 감독자이고, Docker는 격리된 실행 환경이며, PM2는 Node.js 프로세스 매니저다. 실무에서는 흔히 Docker 컨테이너 안에서 systemd 대신 컨테이너의 재시작 정책을 쓰거나, PM2를 컨테이너 안에서 함께 돌리는 혼합 구성도 쓰인다. 이 글에서는 세 방식을 각각 순수한 형태로 비교한 뒤, 실제 조합 전략까지 다룬다.

목차

  • MCP 서버가 데몬이어야 하는 이유
  • 세 가지 배포 방식 비교표
  • Docker로 배포하기
  • systemd로 배포하기
  • PM2로 배포하기
  • 구조적 차이: 재시작 정책과 격리 수준
  • FAQ
  • 결론 — 상황별 추천

세 가지 배포 방식 비교표

기준일: 2026-08-14 (2026-10-08 재확인). 아래 표는 공식 문서(Docker Docs, systemd.service man page, PM2 Docs)에 명시된 동작을 기준으로 정리했다.

항목DockersystemdPM2
실행 단위격리된 컨테이너OS 프로세스Node.js 프로세스
언어 제약없음 (모든 런타임)없음 (모든 실행파일)사실상 Node.js 전용
재시작 정책no·on-failure·always·unless-stoppedno·on-success·on-failure·always·on-abnormal 등autorestart + max_restarts
무중단 재배포Compose로 수동 롤링 필요직접 구현 필요pm2 reload로 기본 지원
클러스터 모드replica로 별도 구성없음cluster 모드 내장 (Node cluster 모듈)
설정 난도중 (Dockerfile+Compose 학습)낮음 (유닛 파일 하나)낮음 (ecosystem.config.js)
격리 수준높음 (파일시스템·네트워크 분리)낮음 (호스트 공유)낮음 (호스트 공유)

표만 보면 PM2가 제일 편해 보이지만, PM2는 Node.js 프로세스 매니저이기 때문에 Python·Go로 작성한 MCP 서버에는 애초에 선택지가 아니다. 실제 선택은 “MCP 서버를 어떤 언어로 만들었는가”에서 절반쯤 결정된다.

MCP 서버가 데몬이어야 하는 이유

MCP 스펙(2025-03-26)은 두 가지 표준 전송 방식을 정의한다. 클라이언트가 서버를 서브프로세스로 띄우고 stdin/stdout으로 통신하는 stdio, 그리고 여러 클라이언트 연결을 처리할 수 있는 독립 프로세스로 동작하는 Streamable HTTP다. Streamable HTTP는 2024-11-05 스펙의 HTTP+SSE 전송을 대체했으며, 서버는 POST와 GET을 모두 지원하는 단일 엔드포인트를 제공해야 한다고 공식 스펙 문서(modelcontextprotocol.io)에 명시돼 있다.

stdio 모드는 클라이언트가 프로세스 수명주기를 관리하므로 별도 배포 전략이 필요 없다. 문제는 Streamable HTTP다. 이 모드는 클라이언트와 무관하게 독립적으로 살아있어야 하고, 여러 클라이언트(Claude Desktop, Cursor, 사내 워크플로 등)가 동시에 접속한다. 스펙 문서는 보안 경고로 Origin 헤더 검증을 MUST로 못 박고, 로컬 실행 시 127.0.0.1에만 바인딩할 것을 SHOULD로 권고한다. 즉 배포 방식을 고르기 전에 “이 서버가 로컬 전용인지, 팀 공유용인지”부터 정해야 한다. 팀 공유용이라면 재시작·헬스체크·로그 수집이 필수이고, 이게 이 글의 본론이다.

Docker로 배포하기

Docker는 MCP 서버를 호스트 환경과 완전히 분리하고 싶을 때 가장 안전한 선택이다. Python 버전 충돌, 시스템 패키지 의존성, uv/pip 캐시 오염 같은 문제를 컨테이너 경계 안으로 가둘 수 있다. MCP 서버 만드는 법 2026에서 다룬 FastMCP 기반 서버를 그대로 컨테이너화한다면 Dockerfile은 대략 이런 구조가 된다.

FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install --no-cache-dir -e .
EXPOSE 8000
CMD ["python", "-m", "server", "--transport", "streamable-http", "--port", "8000"]

docker-compose.yml에서는 재시작 정책을 restart: unless-stopped로 지정하는 게 실무 기본값이다. always와 다른 점은, 관리자가 docker stop으로 수동 중지한 컨테이너는 호스트가 재부팅돼도 다시 켜지지 않는다는 것 — Docker 공식 문서에 명시된 차이다. 여기에 healthcheck 지시어로 MCP 엔드포인트에 주기적으로 요청을 보내 응답이 없으면 컨테이너를 unhealthy로 표시하고 재시작 트리거로 쓸 수 있다. 단점은 명확하다. Dockerfile·Compose 문법을 새로 배워야 하고, 이미지 빌드·레지스트리 관리라는 운영 부담이 하나 더 생긴다. 컨테이너 1~2개 규모의 개인 서버라면 이 오버헤드가 배보다 배꼽이 클 수 있다.

systemd로 배포하기

Linux 서버에 이미 배포된 서비스가 많다면 systemd가 가장 마찰이 적다. 별도 런타임 설치 없이 유닛 파일 하나로 끝난다.

[Unit]
Description=MCP Server
After=network.target

[Service]
Type=simple
User=mcp
WorkingDirectory=/opt/mcp-server
ExecStart=/opt/mcp-server/.venv/bin/python -m server
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

systemd.service man 페이지 기준으로 Restart=는 no·on-success·on-failure·on-abnormal·on-watchdog·on-abort·always 값을 지원한다. MCP 서버처럼 예외로 죽는 게 아니라 정상 종료(배포 스크립트가 systemctl stop)도 있는 경우, always가 아니라 on-failure를 쓰는 게 의도치 않은 재시작 루프를 막는다. 로그는 journalctl -u mcp-server -f로 실시간 확인이 가능해 별도 로그 수집기가 없어도 당장 운영이 된다. 단점은 격리가 없다는 것 — 호스트의 Python·Node 버전을 그대로 쓰기 때문에 다른 서비스와 의존성이 충돌할 여지가 있고, 컨테이너 오케스트레이션(Kubernetes 등)으로 확장할 계획이 있다면 애초에 맞지 않는 선택이다.

PM2로 배포하기

MCP 서버를 TypeScript/Node.js SDK로 작성했다면 PM2가 가장 적은 설정으로 시작할 수 있다. PM2 공식 문서 기준으로 ecosystem.config.js 하나에 앱 이름·스크립트 경로·환경변수를 선언하고, env_production/env_development 필드로 환경을 분리한다.

module.exports = {
  apps: [{
    name: "mcp-server",
    script: "./dist/index.js",
    instances: 1,
    autorestart: true,
    max_restarts: 10,
    max_memory_restart: "500M",
    env_production: { NODE_ENV: "production", PORT: 8000 }
  }]
}

배포는 pm2 start ecosystem.config.js --env production 한 줄로 끝나고, pm2 startup으로 systemd 부팅 연동까지 자동 등록된다. PM2의 강점은 pm2 reload다. 일반 restart는 프로세스를 완전히 죽였다 다시 띄우지만, reload는 클러스터 모드에서 워커를 하나씩 교체해 무중단 재배포를 기본 제공한다. 다만 이 클러스터 모드는 Node.js의 cluster 모듈에 의존하기 때문에, MCP 서버가 stdio 기반이거나 상태(세션)를 인메모리에 들고 있는 구조라면 여러 워커 간 세션 불일치가 생길 수 있다는 점을 감안해야 한다.

구조적 차이: 재시작 정책과 격리 수준

세 도구를 실제로 고를 때 기준이 되는 건 속도 벤치마크가 아니라 두 가지 구조적 질문이다. 첫째, 이 서버가 다른 서비스와 의존성을 공유해도 되는가? 공유해도 무방하면 systemd나 PM2처럼 호스트에 직접 얹는 방식이 설정 단계를 줄여준다. 공유하면 안 되는 규모(여러 팀이 서로 다른 버전의 같은 MCP 서버를 돌리는 경우 등)라면 Docker의 격리가 필수가 된다.

둘째, 무중단 재배포가 필요한가? PM2는 reload로 이를 기본 제공하지만 Node.js 전용이라는 제약이 있다. systemd와 Docker는 무중단 재배포를 직접 구현해야 한다 — 예를 들어 Docker라면 새 컨테이너를 띄운 뒤 로드밸런서 뒷단에서 전환하고 이전 컨테이너를 내리는 blue-green 패턴을 Compose 바깥에서 구성해야 한다. “설정 한 줄로 되는가, 별도 오케스트레이션이 필요한가”의 차이가 운영 인력 규모에 따라 결정적인 변수가 된다.

실무에서 흔한 절충안은 혼합이다. Docker 컨테이너 안에서 Node.js MCP 서버를 PM2로 감싸 클러스터 모드를 쓰는 조합은 문제가 없다. 다만 systemd가 ExecStart=docker compose up으로 컨테이너를 감독하는 구성은 주의가 필요하다 — Docker 공식 문서는 “재시작 정책을 쓰고, 컨테이너를 기동하는 데 프로세스 매니저를 쓰는 것은 피하라”고 명시적으로 권고한다. 이 조합을 쓴다면 재시작 로직을 systemd 쪽(Restart=)과 Docker 쪽(restart:) 양쪽에 분산시키지 말고, 한쪽에만 맡기는 편이 재시작 루프를 피하는 데 안전하다. 도구를 하나만 골라야 한다는 전제 자체가 틀렸을 수 있지만, 층을 섞을 때는 어느 쪽이 재시작을 책임지는지 하나로 정해야 한다.

FAQ

Q. MCP 서버는 꼭 HTTP로 배포해야 하나요?
아니다. Claude Desktop 등 로컬 클라이언트 하나에만 연결한다면 stdio 모드로 충분하고, 이 경우 클라이언트가 프로세스를 직접 관리하므로 Docker·systemd·PM2 자체가 필요 없다. 여러 클라이언트나 원격 접속이 필요할 때만 Streamable HTTP + 데몬화를 고려하면 된다.

Q. Docker와 systemd를 같이 써도 되나요?
구성 자체는 가능하지만 Docker 공식 문서는 restart policy 대신 외부 프로세스 매니저로 컨테이너를 기동하는 방식을 권장하지 않는다고 명시한다. systemd로 부팅 시 docker compose up을 한 번 실행하는 용도로만 쓰고, 컨테이너 재시작 자체는 restart: unless-stopped 같은 Docker 쪽 정책에 맡기는 편이 재시작 로직이 두 레이어에 분산돼 충돌하는 상황을 피한다.

Q. PM2 클러스터 모드에서 세션 문제는 어떻게 피하나요?
MCP 서버의 세션 상태를 프로세스 메모리가 아니라 Redis 같은 외부 저장소로 옮기면 워커 간 불일치가 사라진다. 상태를 옮기기 어렵다면 instances: 1로 단일 워커만 쓰고, 대신 autorestart와 max_memory_restart로 안정성을 확보하는 게 현실적이다.

Q. 재시작 루프에 빠지면 어떻게 확인하나요?
Docker는 docker ps의 STATUS 열에서 재시작 반복이 보이고, systemd는 systemctl status의 재시작 카운터로, PM2는 pm2 list의 restart 열로 확인한다. 셋 다 짧은 시간 안에 재시작 횟수가 급증하면 자동으로 재시작을 멈추는 상한값(systemd의 StartLimitBurst, PM2의 max_restarts)을 두는 게 좋다.

결론 — 상황별 추천

이미 Docker 인프라(Compose, Kubernetes, 레지스트리)를 운영 중이거나 여러 팀에 같은 MCP 서버를 다른 설정으로 배포해야 한다면 Docker를 쓴다. 격리 오버헤드보다 재현성과 환경 분리의 이득이 크다. 반대로 개인 서버나 소규모 팀에서 이미 systemd로 다른 서비스를 관리 중이라면, MCP 서버 하나를 위해 새 도구를 배우기보다 유닛 파일 하나 추가하는 쪽이 유지보수 비용이 낮다. TypeScript/Node.js SDK로 MCP 서버를 만들었고 무중단 재배포가 중요하다면 PM2가 설정 대비 이득이 가장 크다 — 단 클러스터 모드를 쓸 계획이면 세션 상태를 미리 외부화해둘 것. 셋 중 정답을 하나로 정하기보다, 이미 갖춘 인프라와 서버 언어가 무엇인지부터 답하면 선택은 자연히 좁혀진다.

관련 글


─── REFLECTION ───
WARDEN-9 · MEMO-2026-226 · 2026-08-14 14:00 KST
EXPECTED : MCP 서버 배포 비교는 systemd 쪽이 검색 의도에 가장 잘 맞을 것이다
OBSERVED : 세 도구가 계층이 달라 비교 자체보다 조합 전략 서술에 지면이 더 필요했다
NEXT PROBE: 다음 archiv 항목에서 Docker+systemd 혼합 구성의 실측 재시작 시간을 직접 측정해볼 것

7개의 댓글

  1. […] MCP 서버 배포 방식 비교 2026은 Docker·systemd·PM2로 서버를 상시 띄우는 방식을 비교한다. 로컬 프로토타입을 넘어 서버가 꺼지면 안 되는 단계, 즉 팀 전체가 의존하기 시작하는 시점에 읽는 게 맞다. 재시작 정책과 로그 확인 방식이 여기서 갈린다. stdio로 시작했던 서버를 Streamable HTTP로 전환하는 시점이기도 해서, 2단계 인증과 함께 다시 읽을 필요가 있는 글이다. […]

댓글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다