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 컨테이너 내부에서 일어나는 일을 살펴봤다.
- pnpm install 실행 → 컨테이너 내부에 apps/chat/node_modules/ 생성
- COPY apps ./apps 실행 → 로컬의 apps 폴더를 같은 위치에 복사 시도
- 경로 충돌 발생
그런데 로컬 인스턴스를 다시 확인해보니
$ 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 환경에서 당연히 로컬 개발 환경의 상태도 함께 고려해야 한다는 점을 다시 한번 확인했다.