ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 내 컴퓨터에서는 설치되는데 npm ci만 실패하는 이유
    Programming 2026. 9. 29. 10:12
    728x90
    반응형

    로컬은 선언과 잠금 파일이 모두 2.x인데 CI에는 1.x 잠금 파일이 남은 비교도
    CI가 받은 커밋에서 의존성 선언과 잠금 파일의 변경이 함께 들어갔는지 확인합니다.

    package.json의 의존성을 바꾸고 로컬에서 npm install을 실행했습니다. 앱도 잘 뜹니다. 그런데 CI에서는 npm ci가 잠금 파일과 선언이 맞지 않는다며 멈춥니다. 이때 먼저 볼 것은 CI의 엄격함이 아니라 로컬에서 바뀐 package-lock.json까지 커밋에 들어갔는지입니다.

    npm ci 실패 시 lockfile과 package.json·설치 옵션의 일치를 먼저 확인하고 CI에서 lockfile을 임의로 갱신하지 마세요. ci는 현재 선언을 보고 잠금 파일을 고쳐 주는 명령이 아닙니다. 기록된 입력으로 설치할 수 없으면 오류를 내는 동작이 의도돼 있습니다.

    이 글은 2026년 9월 28일 확인한 npm CLI 11 문서를 기준으로 설치 실패를 분류합니다. 아래 버전 변경은 설명용 사례이며 실제 패키지를 설치한 기록은 아닙니다.

    로컬에서 고쳐진 파일이 CI에는 없을 수 있습니다

    가상의 직접 의존성 sample-dependency를 ^1.0.0에서 ^2.0.0으로 바꿨다고 하겠습니다. 기존 lockfile에는 1.4.0이 해결된 버전으로 남아 있습니다. 1.4.0은 새 요구인 2.x를 만족하지 않으므로 두 파일은 어긋납니다.

    npm install은 이런 변경을 반영해 의존성을 해결하고 lockfile을 갱신할 수 있습니다. 반면 npm ci는 불일치를 발견하면 lockfile을 고치는 대신 종료합니다. 기존 package-lock.json 또는 npm-shrinkwrap.json도 필요합니다. npm install의 잠금 파일 처리, npm ci의 입력 조건

    개발자의 폴더에는 새 package.json, 새 lockfile, 새 node_modules가 있지만 커밋에는 package.json만 들어간 상태라면 로컬 성공과 CI 실패가 동시에 설명됩니다. CI가 받은 파일을 기준으로 보면 새 요구와 옛 트리를 조합한 것이기 때문입니다.

    잠금 파일은 단순한 설치 캐시가 아닙니다. npm은 해결된 의존성 트리를 기록해 이후 설치가 같은 트리를 만들 수 있도록 사용합니다. 직접 의존성뿐 아니라 그 아래 패키지 변경도 이 파일에서 검토할 수 있습니다. package-lock.json의 역할

    로그가 불일치를 가리킬 때 비교할 것

    먼저 CI 로그에서 최초 오류를 읽습니다. 선언 불일치인지, 레지스트리 인증 실패인지, 패키지 설치 스크립트의 실패인지에 따라 갈 길이 다릅니다. 어떤 원인이든 잠금 파일부터 삭제하는 습관은 진단에 도움이 되지 않습니다.

    불일치 오류라면 다음 정도를 로컬에서 확인할 수 있습니다.

    node --version
    npm --version
    git status --short -- \
      package.json \
      package-lock.json .npmrc
    git diff -- \
      package.json package-lock.json
    git diff --cached -- \
      package.json package-lock.json
    

    첫 번째 diff는 작업 폴더와 스테이징 사이, 두 번째는 스테이징과 현재 커밋 사이를 보여 줍니다. 로컬에 변경이 있다는 것과 그 변경이 다음 커밋에 들어갈 준비가 됐다는 것을 따로 확인하는 순서입니다. CI가 실행한 커밋 SHA도 함께 확인해야 합니다. 브랜치 이름만 같아서는 같은 파일을 읽었다고 볼 수 없습니다.

    앞의 2.x 변경이 의도한 업그레이드라면 프로젝트에서 정한 Node·npm 버전과 옵션으로 lockfile을 갱신하고 diff를 읽습니다. 함께 바뀐 하위 의존성이 예상보다 많으면 도구 버전이나 설치 정책 변경이 섞였는지도 봅니다. 테스트와 빌드가 끝난 뒤 두 파일을 함께 기록하는 흐름입니다.

    반대로 package.json을 잘못 수정한 경우라면 새 요구에 맞춰 트리를 만드는 것이 해결책은 아닙니다. 어느 버전을 쓰기로 했는지부터 바로잡아야 합니다. CI를 통과시키는 방향으로 파일을 맞추기 전에 의도한 변경인지 먼저 판단하는 이유입니다.

    파일이 같아도 설치 옵션이 다를 수 있습니다

    npm 문서는 --legacy-peer-deps, --install-links처럼 트리의 형태에 영향을 주는 옵션으로 lockfile을 만들었다면 ci에도 같은 옵션을 주도록 안내합니다. 로컬의 사용자 설정에만 옵션이 있고 CI에는 없다면 파일 두 개를 맞춘 뒤에도 실패할 수 있습니다. npm ci의 옵션 주의사항

    이때 필요한 것은 실패한 CI에 옵션을 하나씩 더해보는 일이 아닙니다. 잠금 파일을 만들 때 어떤 정책을 사용했는지 확인하는 일입니다. peer dependency 요구를 우회하도록 설계한 프로젝트인지, 임시로 켰던 옵션이 남은 것인지에 따라 수정 방향이 달라집니다.

    공유할 설치 정책이라면 프로젝트 설정으로 남길 수 있습니다. 다만 .npmrc에는 인증 토큰이 들어갈 수도 있으므로 전체 내용을 진단 로그에 출력하거나 그대로 커밋하지 않습니다. 공유할 옵션과 실행 환경에서 주입할 자격 증명은 분리합니다.

    CLI 버전도 비교 대상입니다. 이 글이 CLI 11 문서를 인용한다고 모든 프로젝트를 11로 바꿔야 한다는 뜻은 아닙니다. 현재 프로젝트가 고정한 버전의 문서를 읽고, 로컬과 CI가 그 버전을 실제로 실행하는지 확인하면 됩니다.

    설치가 끝났는데 테스트 명령이 없을 때

    lockfile에 테스트 도구 이름이 있는데 CI에서 명령을 찾지 못한다면 omit 설정을 봅니다. npm은 생략한 의존성도 lockfile에서 해결하지만 디스크에는 설치하지 않을 수 있습니다. 예를 들어 개발 의존성을 제외한 설치 뒤에는 devDependencies에 둔 도구가 없을 수 있습니다. npm의 omit 설정

    이 경우에는 “잠금 파일에 없다”와 “잠금 파일에는 있지만 이번 설치에서 제외했다”가 다른 원인입니다. 테스트 단계에 필요한 도구까지 생략했다면 그 단계의 설치 범위를 고칩니다. 최종 실행 이미지에서 개발 도구를 제외하는 정책까지 일괄 해제할 필요는 없습니다.

    네이티브 패키지는 OS·CPU 아키텍처·빌드 도구 조건도 확인해야 합니다. 설치 스크립트가 실패했는데 --ignore-scripts로 오류를 없애면, 필요한 바이너리가 만들어지지 않아 실행할 때 다시 막힐 수 있습니다. 이런 실패는 선언과 잠금 파일의 일치 여부만으로 해결되지 않습니다.

    로그를 공유할 때에는 실패한 단계, Node·npm 버전, OS·아키텍처, 실제 옵션을 남기는 편이 유용합니다. “로컬에서는 된다”를 이 정보로 바꾸면 무엇을 같게 맞출지 정할 수 있습니다.

    다시 확인할 때는 새 설치의 조건을 만듭니다

    npm ci는 실행 전에 기존 node_modules를 제거합니다. 읽기 전용 검사가 아니므로, 패키지 폴더 안에 직접 수정한 코드가 있다면 먼저 별도로 보존해야 합니다. 의존성 폴더에 기대지 않는 새 체크아웃이나 일회용 CI 환경이 재설치 확인에 알맞습니다.

    수정 후에는 CI에 올릴 커밋, 정한 도구 버전, 설치 옵션으로 npm ci를 다시 실행합니다. 여기서 성공하면 그 입력으로 설치 단계가 끝났다는 뜻입니다. 이어서 해당 단계에 필요한 테스트나 빌드까지 실행해 사용 가능한 결과인지 확인합니다.

    CI의 명령을 npm install로 바꿔 잠금 파일 갱신을 맡기는 방식은 원래의 검증을 바꿉니다. 의존성을 바꾸려는 작업은 개발 변경으로 검토하고, CI에는 그 결과를 재설치하게 두는 편이 변경 내용을 추적하기 쉽습니다. 실패했던 커밋과 수정한 커밋, 설치 로그를 연결해 두면 다음번에도 같은 종류의 누락을 빠르게 찾을 수 있습니다.

    728x90
    반응형
Designed by Tistory.