[Docker 시리즈 11편] 개발 컨테이너 볼륨으로 코드와 node_modules를 모두 지키기

English version

10편에서 배포 컨테이너를 살펴봤다면, 이번에는 개발용 컨테이너에서 소스 코드를 어떻게 다루는지 알아보겠습니다. Docker에서는 같은 디렉터리를 공유하더라도 "어떤 데이터는 호스트와 공유"하고 "어떤 데이터는 컨테이너 안에만" 두는 방식으로 나눌 수 있습니다. 바인드 마운트, 이름 있는(named) 볼륨, 익명(anonymous) 볼륨의 차이를 이해하면 이런 설계를 직접 할 수 있습니다.

이 글의 흐름

  1. 바인드 마운트·이름 있는 볼륨·익명 볼륨 개념 잡기
  2. 실습: Node.js 개발 컨테이너에서 세 가지를 함께 쓰기
  3. node_modules를 분리해야 하는 대표 패턴
  4. 문제를 예방하는 팁과 볼륨 관리법
  5. 실전에서 바로 써먹는 볼륨 전략 정리

읽기 카드

  • 예상 소요 시간: 15분
  • 사전 준비: Docker Compose 기초, npm 사용 경험
  • 읽고 나면: 바인드 마운트와 익명 볼륨을 언제 분리해야 하는지 설명할 수 있습니다.

세 가지 볼륨 한눈에 보기

  • 바인드 마운트(bind mount): 호스트의 특정 폴더를 컨테이너 경로에 그대로 연결합니다. 예) .:/app
  • 이름 있는 볼륨(named volume): docker volume create mydata처럼 이름을 붙여 Docker가 관리하는 데이터 공간을 씁니다. Compose에서는 volumes: [mydata:/var/lib/mysql]처럼 참조합니다.
  • 익명 볼륨(anonymous volume): 이름을 지정하지 않고 경로만 적습니다. Compose가 임시 이름을 만들어 준 뒤 컨테이너가 사라지면 함께 정리하거나 직접 삭제할 수 있습니다. 예) /app/node_modules

어떤 데이터를 어디에 둘지는 목적에 따라 다릅니다.

  • 코드는 편집기에서 바로 수정해야 하므로 바인드 마운트.
  • 데이터베이스처럼 지속되어야 하는 값은 이름 있는 볼륨.
  • node_modules처럼 OS별 차이가 크고 삭제해도 다시 설치할 수 있는 값은 익명 볼륨.

실습: Node.js 개발 컨테이너

아래 Compose 파일을 직접 실행해 보세요.

services:
  dev:
    image: node:20-alpine
    working_dir: /app
    volumes:
      - .:/app                 # 바인드 마운트
      - node-cache:/root/.npm  # 이름 있는 볼륨
      - /app/node_modules      # 익명 볼륨
    ports:
      - "4321:4321"
    command: sh -c "npm install && npm run dev"

volumes:
  node-cache:
  • .:/app 덕분에 로컬 코드가 컨테이너에 즉시 반영됩니다.
  • node-cache는 npm 캐시를 저장해 다음 설치를 빠르게 합니다.
  • /app/node_modules는 익명 볼륨이라 컨테이너를 지우면 함께 사라져 깨끗한 상태로 다시 설치할 수 있습니다. 팀에 따라서는 익명 볼륨 대신 이름 있는 볼륨을 써서 더 눈에 잘 보이게 관리하기도 합니다.

docker compose up dev로 띄운 뒤 파일을 수정하면 브라우저에서 바로 반영되는지 확인해 보세요. docker volume ls를 보면 node-cache처럼 이름 있는 볼륨과 dev_app-node_modules 같은 익명 볼륨이 함께 생성된 것을 확인할 수 있습니다.

왜 node_modules를 분리해야 할까?

Node.js 프로젝트는 의존성 폴더에 OS별로 다른 바이너리가 들어 있습니다. 호스트와 컨테이너가 리눅스/맥/윈도처럼 섞이면 권한이나 파일 포맷 충돌이 자주 일어납니다. 대표적인 문제는 다음과 같습니다.

  • macOS에서 생성된 node_modules가 리눅스 컨테이너에서 실행될 때 EINVAL: invalid argument 같은 오류 발생
  • Windows 파일 경로 구분자가 하드코딩된 패키지가 컨테이너에서 깨짐
  • node-gyp가 빌드한 바이너리가 OS마다 달라 재설치가 필요함

그래서 실무에서는 보통 이렇게 나눕니다.

  • 소스 폴더: 바인드 마운트로 호스트와 공유
  • node_modules: 익명 볼륨으로 컨테이너 내부에 생성
  • npm 캐시: 이름 있는 볼륨으로 저장해 재설치 속도 개선

익명 볼륨 대신 이름 있는 볼륨을 쓰고 싶다면 이렇게 바꿀 수도 있습니다.

services:
  dev:
    volumes:
      - .:/app
      - node-cache:/root/.npm
      - node-modules:/app/node_modules

volumes:
  node-cache:
  node-modules:

이 방식은 node-modules라는 이름이 눈에 보여서 팀이 함께 관리하기 쉽다는 장점이 있습니다. 반대로 "컨테이너를 지우면 의존성도 같이 날려서 늘 깨끗하게 다시 설치하고 싶다"면 익명 볼륨이 더 단순할 수 있습니다.

필요할 때는 docker compose rm -f dev && docker volume prune으로 익명 볼륨을 정리하고 새로운 의존성을 설치하면 됩니다.

문제를 예방하는 팁

  • 퍼미션 오류: 호스트 UID/GID와 컨테이너가 다르면 파일 소유권이 꼬입니다. chown -R $(id -u):$(id -g) .를 실행하거나 user: "1000:1000" 같은 옵션으로 맞춰 주세요.
  • 볼륨 정리: 익명 볼륨은 이름이 없으니 docker volume ls에서 긴 해시처럼 보입니다. 주기적으로 docker volume prune이나 docker volume rm <ID>로 정리하세요.
  • 핫 리로드 속도: macOS에서 대형 프로젝트를 바인드 마운트하면 느릴 수 있습니다. ./src, ./package.json 등 필요한 경로만 선택적으로 마운트하거나, WSL2/리눅스 환경에서 실행하면 더 빠릅니다. Docker Desktop 환경에서는 마운트 성능 옵션을 추가로 조정하는 경우도 있습니다.
  • 팀 프로젝트: 모든 팀원이 같은 Compose 파일을 쓰면 npm 버전과 Node 버전을 자동으로 맞출 수 있습니다.

실전에서 바로 써먹는 볼륨 전략 정리

개발용 컨테이너에서는 보통 아래 규칙으로 시작하면 안전합니다.

  • 소스 코드: 바인드 마운트
  • 데이터베이스 데이터: 이름 있는 볼륨
  • node_modules 같은 OS 의존 폴더: 익명 볼륨 또는 별도 이름 있는 볼륨

이 세 가지만 구분해도 대부분의 개발용 Compose 파일을 안정적으로 설계할 수 있습니다. 다음 12편에서는 이런 개발·운영 구성이 어디까지 커버 가능한지, 그다음에 어떤 인프라를 배워야 하는지 정리하며 시리즈를 마무리합니다.

💬 댓글

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