[Docker 시리즈 7편] 운영 컨테이너 vs 개발 컨테이너, Node 앱으로 이해하기

English version

6편에서 Compose 파일을 만드는 방법을 익혔다면 이제 "같은 코드지만 다른 규칙으로 실행되는 두 컨테이너"를 비교할 차례입니다. Node.js 앱을 예로 들면 dev/prod 차이가 훨씬 또렷하게 보입니다. 이번 글은 일반적인 Node 앱을 기준으로 운영 컨테이너와 개발 컨테이너를 나눠 봅니다.

이 글의 흐름

  1. Node 앱 기준으로 운영 컨테이너를 설계하는 기본 규칙
  2. 개발 컨테이너에서 의도적으로 열어 두는 요소
  3. 명령·포트·볼륨을 비교하는 3단 체크리스트
  4. Mini Lab: 두 컨테이너를 번갈아 실행해 보기
  5. 초보자가 꼭 구분해야 할 핵심 한 줄

읽기 카드

  • 예상 소요 시간: 18분
  • 사전 준비: npm dev 서버 실행 경험, Compose 명령 기초
  • 읽고 나면: 하나의 코드베이스에서 dev/prod 컨테이너를 구분해 운영할 수 있습니다.

운영 컨테이너: 빌드된 산출물만 포함하기

운영 환경에서는 "바뀌지 않는 것"이 장점입니다. 아래 두 파일을 기준으로 생각해 보세요.

# Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/dist ./dist
COPY package*.json ./
RUN npm ci --omit=dev
CMD ["node", "dist/server.js"]
# docker-compose.prod.yml
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
      target: runner
    ports:
      - "8080:3000"
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://127.0.0.1:3000/health"]
      interval: 30s
      timeout: 3s
      retries: 3
  • 이미지는 멀티 스테이지로 빌드되어 dist/ 결과물만 포함합니다.
  • npm ci --omit=dev를 사용해 개발 의존성을 제거합니다.
  • ports에서는 외부 8080 → 내부 3000만 열어 두고, 나머지 포트는 감춥니다.
  • restart, healthcheck 같은 정책을 추가해 자동 복구를 준비합니다.

핵심 문장은 하나입니다. 운영 컨테이너는 단단할수록 좋다. 파일을 수정하거나 npm install을 다시 할 수 없도록 만드는 것이 오히려 안전합니다.

개발 컨테이너: 빠르게 고칠 수 있어야 한다

개발 환경에서는 "즉시 반영"이 가장 중요합니다. 그래서 세 가지를 의도적으로 열어 둡니다.

services:
  app-dev:
    image: node:20-alpine
    working_dir: /app
    command: sh -c "npm install && npm run dev"
    volumes:
      - .:/app
      - /app/node_modules
    ports:
      - "5173:5173"
    environment:
      - NODE_ENV=development
    profiles: ["dev"]
  • volumes로 로컬 폴더를 그대로 올리고, 익명 볼륨으로 node_modules만 컨테이너 내부에 유지합니다.
  • command로 npm dev 서버를 실행해 핫 리로드를 지원합니다.
  • profiles 덕분에 docker compose up 기본 실행에서는 개발 컨테이너가 뜨지 않습니다.

이 상태를 프로덕션에 들고 가면 npm install이 매번 실행되고, 코드가 실시간으로 변하기 때문에 장애가 나도 재현하기 어렵습니다. 그래서 dev 컨테이너는 반드시 별도 프로필이나 별도 Compose 파일로 분리해야 합니다.

3단 체크리스트: 명령 · 포트 · 볼륨

항목 운영 컨테이너 개발 컨테이너
명령 Dockerfile CMD 그대로 실행, 변경 금지 Compose command로 dev 서버 실행
포트 필요한 최소 포트만 노출(예: 8080:3000) 개발 서버 기본 포트 그대로 노출(예: 5173:5173)
볼륨 읽기 전용 자산, 혹은 아예 없음 소스 폴더 바인드 + 의존성 익명 볼륨

이 표를 빠르게 점검해 보면 "지금 띄운 컨테이너가 진짜 운영인지" 헷갈리지 않습니다.

Mini Lab: 두 컨테이너 번갈아 실행하기

  1. 위 dev/prod Compose 스니펫을 compose.prod.yml, compose.dev.yml 두 파일에 나눠 적습니다.
  2. docker compose -f compose.prod.yml up -d로 운영 버전을 띄운 뒤 curl localhost:8080으로 확인합니다.
  3. docker compose -f compose.dev.yml --profile dev up -d를 실행해 개발 서버도 띄우고, docker compose ps로 포트가 어떻게 다른지 비교합니다.
  4. 코드 한 줄을 수정한 뒤 브라우저를 새로고침해 dev 컨테이너의 즉시 반영을 확인합니다.
  5. 마지막으로 docker compose -f compose.dev.yml --profile dev down으로 개발 컨테이너만 종료합니다.

이 실습을 반복하면 "운영 컨테이너는 이미지가 바뀌지 않는 한 상태가 변하지 않는다"는 사실이 서서히 체감됩니다.

초보자가 꼭 구분해야 할 핵심 한 줄

정리하면 아주 단순합니다.

  • 운영 컨테이너는 바뀌지 않아야 합니다.
  • 개발 컨테이너는 빨리 바뀌어야 합니다.

이 한 줄만 기억해도 dev/prod 설정이 왜 갈라지는지 대부분 설명할 수 있습니다. 다음 8편에서는 이 두 컨테이너가 실제로 어떤 포트와 네트워크를 통해 통신하는지, 그리고 호스트에서 어떻게 접근할 수 있는지를 더 자세히 다룹니다.

💬 댓글

이 글에 대한 의견을 남겨주세요