tech.junha.kr

Docker COPY에서 node_modules 충돌 해결하기

turborepo + pnpm 환경을 도커 설정을 수정하다가 흥미로운 에러를 만났다.

ERROR: failed to solve: cannot copy to non-directory:
/var/lib/docker/overlay2/.../app/apps/chat/node_modules/typescript

node_modules 관련 충돌인 건 분명한데, git에는 node_modules가 없는 상황이라 원인 파악이 필요했다.

상황 분석

현재는 캐시 최적화를 위해 다음과 같은 Dockerfile 구조를 사용하고 있었다.

COPY apps/chat/package.json ./apps/chat/package.json
RUN pnpm install  
COPY apps ./apps  # 여기서 에러 발생

git 상태를 확인해보니 깨끗했다:

$ git status  
nothing to commit, working tree clean

$ ls apps/chat/
src/ public/ package.json # node_modules 보이지 않음

그런데 왜 충돌이 발생할까?

원인 발견

Docker 컨테이너 내부에서 일어나는 일을 살펴봤다.

  1. pnpm install 실행 → 컨테이너 내부에 apps/chat/node_modules/ 생성
  2. COPY apps ./apps 실행 → 로컬의 apps 폴더를 같은 위치에 복사 시도
  3. 경로 충돌 발생

그런데 로컬 인스턴스를 다시 확인해보니

$ ls -la apps/chat/
drwxr-xr-x  3 user user 4096 Apr 23 15:05 node_modules
-rw-r--r--  1 user user 1122 May  9 15:40 package.json

아, 로컬 개발 과정에서 생성된 node_modules가 실제로 존재했다. .gitignore로 git에는 올라가지 않았지만 파일시스템에는 있었던 것이다..!

.dockerignore 이슈

당연히 .dockerignore를 설정해뒀다.

node_modules/
**/node_modules/
apps/*/node_modules/

그런데 왜 node_modules가 복사가 된될까? .dockerignore는 COPY . . 같은 명령어에서 해당 파일들을 아예 제외해야 하는데 말이다.

Docker 빌드 캐시나 pnpm의 특수한 링크 구조 때문에 제대로 작동하지 않는 것 같았다. --no-cache 옵션으로 빌드해봐도 동일한 에러가 발생했다.

해결 방법

임시 해결

우선 로컬 node_modules를 제거하고 테스트해봤다:

mv apps/chat/node_modules apps/chat/node_modules.bak
docker build -t test .  # 성공!

예상대로 충돌할 파일이 없으니 정상 빌드됐다.

포인트 짚고 넘어가기

Docker COPY의 동작

  • 같은 경로에 다른 타입(파일 vs 폴더)이 있으면 충돌 발생
  • .dockerignore가 항상 예상대로 작동하지 않을 수 있음

pnpm workspace 특성

  • 복잡한 심볼릭 링크 구조로 일반적인 npm과 다른 동작
  • Docker 환경에서는 추가 고려사항 필요

실무 관점

  • 이론적 최적화보다 안정적 빌드가 우선
  • 로컬 개발 환경의 부산물도 Docker 빌드에 영향

마무리

git에는 없지만 로컬 파일시스템에 존재하는 node_modules가 원인이었고, .dockerignore가 예상대로 작동하지 않는 상황이 겹쳤다. 결국 node_modules를 없애서 해결한 간단한 문제였다. Docker 환경에서 당연히 로컬 개발 환경의 상태도 함께 고려해야 한다는 점을 다시 한번 확인했다.