npm ci가 실패할 때는 package-lock.json 불일치와 설치 옵션 차이를 먼저 구분해야 한다. 로컬에서 npm install이 성공해도 CI에서는 같은 결과가 나오지 않을 수 있다. 잠금 파일을 바로 삭제하기보다 파일 상태, Node.js·npm 버전, 프로젝트 설정을 차례로 확인하면 수정 범위를 좁히기 쉽다.
npm ci의 역할: 잠금 파일을 고치는 대신 설치를 검증한다
npm ci는 잠금 파일에 기록된 의존성으로 프로젝트를 설치하는 명령이다. package.json과 잠금 파일의 의존성이 맞지 않으면 파일을 자동으로 수정하지 않고 오류로 종료한다. 기존 node_modules가 있으면 설치 과정에서 제거하므로, 작업 중인 로컬 환경에서는 재설치가 일어난다는 점도 고려해야 한다.
반면 npm install은 잠긴 버전이 package.json의 요구 범위를 충족하지 않으면 의존성을 다시 해석하고 잠금 파일을 갱신한다. 따라서 “로컬 설치 성공”과 “저장소에 커밋된 파일만으로 재현 성공”은 따로 확인할 필요가 있다. 출처: [npm 공식 npm install 문서](https://docs.npmjs.com/cli/v11/commands/npm-install/).
📌 핵심 요약
- 불일치 오류라면 package.json 변경과 잠금 파일 누락부터 확인한다.
- 같은 파일인데 결과가 다르면 npm 버전과 설치 옵션을 비교한다.
- 잠금 파일을 갱신한 뒤에는 npm ci와 실제 빌드·테스트까지 다시 실행한다.
npm ci의 역할: 잠금 파일을 고치는 대신 설치를 검증한다 실시간 확인하기
1. package-lock.json 존재와 CI 실행 경로부터 확인하기
먼저 실패한 작업이 어느 디렉터리에서 실행되는지 확인한다. 저장소에 잠금 파일이 있어도 GitHub Actions의 작업 디렉터리나 Docker 이미지에 복사된 경로가 다르면 해당 파일을 사용하지 못한다. 모노레포에서는 저장소 루트와 개별 패키지 디렉터리를 혼동하기 쉬우므로, 설치 명령의 실행 위치부터 맞추는 편이 좋다.
- 실행 디렉터리에
package.json과package-lock.json이 함께 있는지 확인한다. - 잠금 파일이 로컬에만 존재하는지, 실제 CI 대상 커밋에도 포함됐는지 확인한다.
- 최근 의존성 추가·삭제·버전 범위 변경과 잠금 파일 변경이 함께 반영됐는지 확인한다.
- 오류 로그에서 처음 실패한 명령과 의존성 이름을 기록한다.
git status --short
git ls-files package.json package-lock.json .npmrc
git diff -- package.json package-lock.json
git diff는 아직 커밋하지 않은 변경을 확인하는 용도다. CI가 이전 커밋이나 다른 브랜치를 실행한다면 로컬 파일이 정상이어도 실패가 이어질 수 있으므로, 실행 대상 커밋도 함께 비교한다.
npm 9 공식 문서는 package-lock.json 또는 npm-shrinkwrap.json을 요구한다고 명시한다. shrinkwrap을 사용하는 기존 프로젝트라면 사용 중인 npm 버전의 문서와 실제 잠금 파일을 기준으로 점검해야 한다. 출처: [npm 9 공식 npm ci 문서](https://docs.npmjs.com/cli/v9/commands/npm-ci/).
1. package-lock.json 존재와 CI 실행 경로부터 확인하기
2. 로컬과 CI의 Node.js·npm 버전 및 설치 옵션 비교하기
파일이 정상이라면 환경을 기록할 차례다. 로컬과 CI 양쪽에서 아래 명령을 실행하고, 출력값을 같은 작업 로그에 남긴다. “Node.js는 같다”는 기억보다 정확한 버전 문자열이 원인 분석에 도움이 된다.
node --version
npm --version
npm config get legacy-peer-deps
npm config get install-links
npm config get strict-peer-deps
npm config get omit
npm config get include
npm config get ignore-scripts
특히 잠금 파일을 만들 때 --legacy-peer-deps 또는 --install-links처럼 의존성 트리 구성에 영향을 주는 옵션을 사용했다면, npm ci에도 같은 설정이 필요하다. 명령줄 옵션뿐 아니라 프로젝트 .npmrc, 사용자 설정, CI 환경 변수에 차이가 있는지도 확인한다.
예를 들어 프로젝트가 이미 legacy-peer-deps=true를 전제로 관리되고 있다면 다음 명령으로 프로젝트 설정을 저장할 수 있다. 오류를 없애기 위해 새로 추가하기보다는, 잠금 파일 생성 조건을 확인한 뒤 적용하는 방식이 적절하다.
npm config set legacy-peer-deps=true --location=project
이 경우 생성·변경된 .npmrc도 저장소에 반영해야 CI에서 설정을 재현할 수 있다. 단, 인증 토큰 같은 비밀값은 커밋 대상에서 제외하고 CI의 비밀 변수로 관리한다.
strict-peer-deps의 기본값은 false다. 이를 true로 설정하고 legacy-peer-deps를 사용하지 않으면 피어 의존성 충돌이 설치 실패로 처리되므로, 로컬과 CI의 값이 다른지 살펴야 한다. 기본값이 false라는 이유만으로 모든 피어 의존성 충돌이 해결되는 것은 아니다.
2. 로컬과 CI의 Node.js·npm 버전 및 설치 옵션 비교하기
3. 의존성 불일치와 devDependencies 생략·스크립트 실패 구분하기
오류 로그의 마지막 줄만 보면 서로 다른 문제가 모두 “설치 실패”로 보인다. 먼저 잠금 파일 검증에서 멈췄는지, 패키지를 가져오는 중인지, 설치 스크립트나 이후 빌드에서 실패했는지 나눈다. 아래 표는 해결 순서를 정하기 위한 진단 기준이다.
| 로그·증상 | 우선 확인할 항목 |
|---|---|
| package.json과 lock 파일이 맞지 않는다는 메시지 | 의존성 변경 누락, 잘못 병합된 잠금 파일, 실행 경로 |
| ERESOLVE와 피어 의존성 충돌 | 충돌 패키지의 요구 범위, legacy-peer-deps·strict-peer-deps 설정 |
| 설치 후 빌드 도구를 찾지 못함 | NODE_ENV, omit·include, 도구의 devDependencies 등록 여부 |
| install·postinstall 중 종료 | 해당 스크립트 출력, 운영체제·네이티브 빌드 환경 |
| 인증·다운로드·외부 소스 접근 오류 | 레지스트리 인증, 네트워크, npm 버전별 접근 정책 |
NODE_ENV=production이면 omit 기본값은 dev다. 이때 개발 의존성은 잠금 파일에 기록돼 있어도 디스크에 설치되지 않을 수 있으며, 같은 유형이 --include에도 있으면 설치된다. 빌드 단계에 개발 도구가 필요하다면 npm ci --include=dev 적용 여부를 검토한다.
설치 스크립트에서 실패한다면 npm ci --foreground-scripts로 출력을 확인한다. ignore-scripts 기본값은 false이며, 스크립트를 무조건 생략하면 필요한 바이너리나 생성물이 빠질 수 있다. 설치 명령의 종료 상태와 뒤이어 실행되는 빌드·테스트의 종료 상태도 따로 구분한다.
3. 의존성 불일치와 devDependencies 생략·스크립트 실… 지원하기
4. 2026년 npm 12에서는 외부 의존성 접근 설정도 확인하기
2026년 11월 확인한 npm 12 문서에서 allow-git과 allow-remote의 기본값은 모두 none이다. 다만 설정된 레지스트리와 호스트명이 같은 tarball은 일반 레지스트리 설치 대상으로 취급된다. Git 저장소나 별도 호스트의 tarball을 사용하는 프로젝트는 npm 업그레이드 전후의 설정 차이를 확인할 필요가 있다.
npm config get registry
npm config get allow-git
npm config get allow-remote
이 점검은 해당 옵션을 지원하는 npm 버전에서 수행한다. 접근 제한 메시지가 확인되면 의존성의 출처와 프로젝트 정책을 검토해 필요한 허용 범위를 정한다. 잠금 파일을 다시 만들어도 접근 정책 자체가 달라지지는 않으므로, 파일 불일치와 별개로 처리해야 한다.
실무적으로 재현 가능한 설치는 잠금 파일뿐 아니라 실행 도구와 설정까지 함께 관리할 때 유지된다. 특히 npm 버전 업그레이드를 의존성 수정과 한꺼번에 진행하면 실패 원인이 섞이기 쉬우므로, 버전 변경과 패키지 변경을 구분해 검증하는 편이 분석에 유리하다. 관련 기본값과 동작의 근거: [npm 12 공식 npm ci 문서](https://docs.npmjs.com/cli/v12/commands/npm-ci/).
4. 2026년 npm 12에서는 외부 의존성 접근 설정도 확인하기
5. package-lock.json 갱신 후 npm ci 재검증과 커밋하기
실제 의존성 불일치가 확인됐다면 먼저 package.json의 변경이 의도된 것인지 판단한다. 실수로 바뀐 선언이라면 해당 변경을 복구하고, 필요한 의존성 변경이라면 프로젝트에서 사용하는 npm 버전과 설치 옵션으로 잠금 파일을 갱신한다. 기존 잠금 파일을 유지한 상태에서 시작하면 불필요한 버전 변경을 줄이는 데 도움이 된다.
npm install
git diff -- package.json package-lock.json .npmrc
npm ci
위 명령은 필요한 프로젝트 설정이 이미 적용돼 있다는 전제다. npm install 뒤에는 추가·삭제된 패키지와 예상 밖 버전 변경을 검토하고, npm ci 뒤에는 프로젝트에 정의된 빌드·테스트를 실행한다. 설치 성공만으로 애플리케이션 동작까지 검증됐다고 판단하지 않는다.
수정의 목적은 CI를 한 번 통과시키는 데서 끝나지 않는다. 다음 실행에서도 같은 조건으로 설치되는지 확인해야 한다. CI의 npm ci를 npm install로 바꾸기 전에 파일과 설정의 불일치를 해결하면, 실패가 알려 준 재현성 문제를 추적할 수 있다.
- package.json: 의도된 의존성 선언 변경이 있을 때 커밋한다.
- package-lock.json: 갱신 결과를 검토하고 반드시 함께 반영한다.
- .npmrc: 재현에 필요한 프로젝트 옵션을 변경했다면 커밋한다.
- CI 설정·Dockerfile·버전 관리 파일: Node.js·npm 버전이나 설치 명령을 수정했다면 함께 반영한다.
- 최종 확인: 같은 커밋으로 CI를 다시 실행하고 설치·빌드·테스트 결과를 확인한다.
확인 순서는 실행 경로와 파일 → Node.js·npm 버전 → 설치 옵션 → 오류 단계 → 필요한 잠금 파일 갱신이다. 이 순서를 지키면 단순한 커밋 누락과 환경 차이를 구분하면서, 수정 후 함께 저장해야 할 파일까지 명확하게 정리할 수 있다.