본문 바로가기
Tools2026년 8월 1일5분 읽기

잠금 파일과 재현 가능한 빌드 — npm ci를 써야 하는 이유

YS
김영삼
조회 4
잠금 파일과 재현 가능한 빌드 — npm ci를 써야 하는 이유

잠금 파일(lockfile)은 package.json의 느슨한 버전 범위를 실제로 설치된 정확한 버전과 무결성 해시로 못 박아 기록한 파일이다. package-lock.json, pnpm-lock.yaml, yarn.lock이 여기 해당한다. 이게 있어야 "내 컴퓨터에선 되는데" 문제를 재현 가능한 빌드로 바꿀 수 있다.

내가 잠금 파일을 진지하게 대하게 된 계기가 있다. CI에선 통과하던 테스트가 동료 로컬에선 깨졌다. 원인은 어처구니없었다. 그 사이 어떤 하위 의존성이 patch 버전을 올렸고, 둘의 설치 시점이 달라 다른 코드가 깔린 것이다. 잠금 파일을 커밋하고 npm ci로 바꾼 뒤 그런 유령 버그가 사라졌다.

왜 package.json만으론 부족한가

package.json에는 보통 이렇게 쓴다.

"dependencies": {
  "lodash": "^4.17.0"
}

^4.17.0은 "4.17.0 이상, 5.0.0 미만이면 뭐든 OK"라는 범위다. 오늘 설치하면 4.17.21, 다음 달엔 4.17.22가 깔릴 수 있다. 게다가 lodash가 의존하는 하위 패키지들, 또 그 하위의 하위까지 전부 각자 범위를 가진다. 이 거대한 트리 전체가 "설치 시점"에 따라 미묘하게 달라진다.

잠금 파일은 이 트리를 특정 순간의 스냅샷으로 고정한다. 각 패키지의 정확한 버전, 내려받을 URL, 그리고 위변조를 검증할 integrity 해시(예: sha512)까지 적는다. 그래서 누가 언제 설치하든 바이트 단위로 동일한 의존성 트리가 만들어진다.

install과 ci의 결정적 차이

여기서 실무의 핵심이 갈린다. 잠금 파일을 대하는 방식이 명령마다 다르다.

항목npm installnpm ci
잠금 파일필요 시 갱신있는 그대로 따름
불일치 시package.json에 맞춰 수정에러로 중단
node_modules부분 갱신지우고 새로 설치
권장 위치로컬 개발CI·배포

규칙은 외우기 쉽다. 로컬에서 의존성을 바꿀 땐 install, CI/배포에선 ci. npm ci는 잠금 파일과 package.json이 어긋나면 아예 실패한다. 이게 방어선이다. 누군가 잠금 파일을 커밋 안 하고 package.json만 바꿨다면 CI가 즉시 막아준다.

# CI 파이프라인에서
npm ci            # 잠금 파일 그대로, 깨끗한 설치
npm run build
npm test

실전 규칙 몇 가지

  • 잠금 파일은 반드시 커밋한다. 라이브러리가 아니라 애플리케이션이라면 예외 없이. .gitignore에 넣는 건 재현성을 스스로 버리는 짓이다.
  • 패키지 매니저를 하나로 통일한다. npm과 pnpm과 yarn의 잠금 파일이 저장소에 뒤섞이면 재앙이다. package.jsonpackageManager 필드로 못 박고, corepack으로 강제하는 걸 권한다.
  • 잠금 파일 충돌은 재생성으로 푼다. 병합 충돌이 났을 때 손으로 고치지 말고, package.json을 먼저 병합한 뒤 npm install로 잠금 파일을 다시 만든다.
  • integrity 해시를 신뢰한다. 잠금 파일의 해시는 공급망 공격 방어의 일부다. 설치 시 내려받은 패키지가 기록된 해시와 다르면 설치가 거부된다.
몇 번 데인 뒤에야 습관이 됐다. 나는 이제 package.json과 잠금 파일을 항상 같은 커밋에 함께 올린다. 둘 중 하나만 바뀐 PR은 리뷰에서 되돌려 보낸다.

pnpm·yarn도 원리는 같다

파일 이름과 포맷만 다를 뿐 개념은 동일하다. pnpm은 pnpm-lock.yamlpnpm install --frozen-lockfile, yarn은 yarn.lockyarn install --immutable이 각각 npm ci에 대응한다. CI에선 이 "얼린" 모드를 쓰는 게 핵심이다.

자주 묻는 질문

잠금 파일을 .gitignore에 넣어도 되나요?

애플리케이션이라면 안 됩니다. 잠금 파일을 커밋하지 않으면 설치 시점마다 의존성 트리가 달라져 재현성이 사라집니다. 다만 공개 라이브러리를 배포하는 경우, 소비자는 자기 잠금 파일을 쓰므로 라이브러리 저장소에서는 커밋하지 않는 관례도 있습니다.

npm install과 npm ci를 언제 구분해 쓰나요?

의존성을 추가·변경하는 로컬 작업에는 npm install을, 있는 그대로 재현해야 하는 CI와 배포에는 npm ci를 씁니다. ci는 node_modules를 지우고 잠금 파일 그대로 설치하며, 잠금 파일이 없거나 package.json과 어긋나면 실패합니다.

잠금 파일에 병합 충돌이 났어요.

수동으로 편집하지 마세요. 먼저 package.json의 충돌을 해결한 뒤 npm install을 다시 실행하면 잠금 파일이 올바르게 재생성됩니다. pnpm·yarn도 같은 방식으로 잠금 파일을 재생성해 해결합니다.

integrity 해시는 무슨 역할인가요?

내려받은 패키지 내용이 기록된 값과 일치하는지 검증하는 무결성 체크섬입니다. 레지스트리가 침해되거나 중간에서 패키지가 변조되면 해시가 어긋나 설치가 거부됩니다. 공급망 공격을 막는 기본 방어선이라 잠금 파일을 커밋해두는 것이 보안상으로도 이득입니다.

댓글 0

아직 댓글이 없습니다.
Ctrl+Enter로 등록