본문 바로가기
Language/JavaScript

npm install vs npm ci: CI 환경에서 package-lock의 진짜 역할

by J4J 2026. 2. 19.
300x250
반응형

안녕하세요. J4J입니다.

 

이번 포스팅은 npm install과 npm ci의 비교, package-lock의 역할에 대해 적어보는 시간을 가져보려고 합니다.

 

 

 

npm install과 package.json

 

node 기반의 프로젝트를 개발하는 곳에서 많이 입력하는 명령어 중 하나는 npm install입니다.

 

많은 사람들이 아는 것처럼 npm install은 새로운 패키지를 설치할 때 사용되기도 하고, 새로운 프로젝트를 내려받은 뒤 의존되어 있는 모듈들을 node_modules에 구성하기 위해 사용되기도 합니다.

 

그래서 어느 상황이든 의존되어 있는 모듈들을 설치해야 한다고 할 때 npm install 만을 사용하는 실수를 범할 수 있습니다.

 

 

 

의존성을 현명하게 설치하기 위해서는 npm install의 동작 방식에 대해서 한번쯤 이해하는 시간이 필요합니다.

 

npm install은 package.json에 정의되어 있는 모듈들을 설치하는 것이 전부라고만 생각할 수 있습니다.

 

하지만, npm install 명령이 수행될 때 다음과 같은 절차를 가지게 됩니다.

 

  1. package.json 읽음
  2. package-lock.json 읽음
  3. 의존성 판단 후 node_modules 생성
  4. package-lock.json 업데이트 (업데이트가 가능한 경우만)

 

 

npm install을 하는 경우 package.json에 정의되어 있는 모듈들을 설치하는 것은 맞습니다.

 

다만, package.json에 정의되어 있는 버전들을 항상 그대로 사용하는 것은 아닙니다.

 

예를 들어, 다음과 같이 package.json에 react 버전을 19.2.3으로 했을 때 실제로 설치되는 버전은 19.2.3 일 수도 있고 아닐 수도 있다는 뜻이 됩니다.

 

// package.json
{
  "dependencies": {
    "react": "^19.2.3"
  }
}

 

 

 

또한 package-lock에 대해서도 생각해 봐야 합니다.

 

package-lock의 역할은 무엇인지, package.json은 변경되지 않은 상황에서 npm install을 하게 되면 package-lock이 한 번씩 업데이트가 발생하는 이유는 무엇인지 모두 이해할 필요가 있습니다.

 

 

반응형

 

 

^과 ~

 

가장 먼저 ^(caret)과 ~(tilde)가 의미하는 것이 무엇인지를 이해해야 합니다.

 

우리는 패키지를 설치할 때 버전 정보만 입력하지만 package.json에 정의된 의존성을 확인하면 버전 정보 앞에 ^과 ~이 붙어 있는 것을 확인할 수 있습니다.

 

이들을 이해하기 위해서는 semVer에 대한 이해가 필요합니다.

 

 

 

semVer은 {major}.{minor}.{patch}의 형태로 버전 관리를 하는 것을 의미합니다.

 

major가 변경되면 이전 버전과 호환성이 맞지 않은 큰 변화가 발생된 것입니다.

 

minor는 호환성을 가지며 신규 기능이 추가된 것이며, patch는 버그 수정한 것을 의미합니다.

 

그래서 major가 변경되는 경우 기존 사용했던 기능들의 구조가 바뀔 수 있기 때문에 전체적인 소스 코드의 변화를 겪을 수 있게 됩니다.

 

하지만 minor, patch 등이 변경되는 경우에는 이런 변화를 겪을 확률이 굉장히 작아집니다.

 

 

 

^(caret)의 경우는 package.json에 정의된 버전을 기준으로 minor, patch의 변경점을 모두 허용하는 것을 의미합니다.

 

그래서 package.json에 react의 버전이 ^19.2.3와 같이 구성되어 있다면 19.x.x의 형태로 되어 있는 범위는 모두 허용하는 것을 의미합니다.

 

그러므로 npm install을 하는 시점을 기준으로 >=19.2.3과 <20.0.0 범위 안에 들어 있는 가장 최신 버전을 설치하게 됩니다.

 

 

 

~(tilde)의 경우는 patch 변경점까지만 허용하는 것을 의미합니다.

 

^(caret)과 동일한 예시로 설명을 해보면 19.2.x의 형태로 되어 있는 범위까지만 허용하는 것을 의미하게 됩니다.

 

그러므로 package.json에 ~19.2.3과 같이 구성되어 있다면, npm install을 하는 시점에 >=19.2.3과 <19.3.0 범위 안에 들어 있는 가장 최신 버전을 설치하게 됩니다.

 

 

 

 

package-lock.json

 

다음으로 package-lock.json의 역할에 대한 이해가 필요합니다.

 

package-lock은 현재 프로젝트에 설치된 의존성의 정확한 버전 조합들을 관리하는 파일입니다.

 

 

 

이해하기 쉽게 계속 언급하고 있던 react 버전으로 예시를 들어 보겠습니다.

 

현재 시점을 기준으로 package.json에 다음과 같이 react 버전을 정의해 둔 뒤 node_modules, package-lock.json이 없는 상황에서 npm install을 하면 결과는 다음과 같이 나옵니다.

 

// package.json
{
  "dependencies": {
    "react": "^19.2.3"
  }
}

 

// package-lock.json
"packages": {
    "node_modules/react": {
      "version": "19.2.4",
      "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
      "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
      "license": "MIT",
      "engines": {
        "node": ">=0.10.0"
      }
    }
}

 

 

 

설치하려는 react는 19.2.3 버전이지만 ^(caret)으로 버전이 명시되어 있기에 범위 안에 포함되는 가장 최신 버전인 19.2.4 버전이 설치가 된 것입니다.

 

그리고 package-lock에서 실제로 설치된 19.2.4 버전을 확인할 수 있습니다.

 

이처럼 프로젝트에 사용되는 의존 패키지들의 실제 버전은 package-lock이 보관하고 있는 것입니다.

 

 

 

package-lock의 역할은 의존성 트리를 정확히 고정하여 동일한 설치 결과를 보장하는 것입니다.

 

그래서 package-lock이 없다면 다음과 같은 문제를 발생시키게 됩니다.

 

  • npm install 할 때마다 실제 설치되는 버전이 달라짐
  • 개발할 때는 버전 문제가 없었지만, 나중에 다른 사람이 내려받아 사용할 때는 버전 문제가 발생할 수 있음
  • 또는 배포할 때도 버전 문제가 발생할 수 있음
  • 등등...

 

 

그러므로 이런 문제를 발생시키지 않기 위해 package-lock은 항상 존재해야 하며, git을 통해 다른 사람들과도 항상 공유가 되어야만 합니다.

 

 

 

 

npm ci

 

npm ci도 npm install과 같이 의존성을 설치하는 명령어 중 하나입니다.

 

하지만 가장 큰 차이점은 npm ci는 package-lock을 이용하여 의존성을 설치하는 것입니다.

 

 

 

npm ci 명령이 수행될 때는 다음과 같은 절차를 가지게 됩니다.

 

  1. node_modules가 있는 경우 전부 삭제
  2. package-lock.json에 명시된 의존성 설치
  3. package.json과 package-lock.json을 비교하여 버전 계산이 다른 경우 에러 발생
  4. 이 외 package-lock 파일 등의 버전 값 변경 행위를 절대로 하지 않음

 

 

즉, npm ci를 사용하게 된다면 실제로 우리가 개발할 때 사용된 의존성의 버전 정보를 그대로 활용하여 재 설치 하는 것을 도와줍니다.

 

npm install처럼 어느 순간에 사용되었는지에 따라 버전이 달라지는 것 없이 package-lock의 정보를 읽어 항상 동일하게 설치하는 것을 보장해 줍니다.

 

 

 

npm ci를 가장 필요로 하는 곳은 이름에서 알 수 있는 것처럼 CI 환경이라고 얘기할 수 있습니다.

 

그리고 애초에 CI 환경에서 재현 가능한 의존성 설치를 보장하기 위해 만들어진 명령어이기도 합니다.

 

CI 파이프라인을 구성하고 있는 팀에서 npm install을 사용하고 있다면 한 번씩 build 에러를 경험하게 됩니다.

 

파이프라인이 동작하는 시점에 따라 버전이 변경되어 의존성이 충돌될 수 있고, 사용 방식이 변경되는 경험도 한 번씩 겪을 수 있습니다.

 

그러므로 만약 npm install을 이용하여 CI를 구성하고 있다면 지금 당장 npm ci로 변경하는 것을 적극 권장합니다.

 

 

 

 

install과 ci를 사용하는 시점

 

여기까지 읽게 된다면 npm install과 npm ci가 어떤 과정을 거쳐 의존성을 설치하는지 이해가 모두 되셨을 거라고 생각합니다.

 

최종적으로 install과 ci를 언제 사용하는 것이 바람직한 것인지 정리해 보겠습니다.

 

 

 

[ npm install ]

 

npm install을 사용하는 가장 바람직한 시점은 로컬 개발을 할 때입니다.

 

명령어를 사용하는 시점에 따라 실제 사용 버전을 변경하는 상황이 발생하지만, 범위에 따른 버전 변경도 필요한 기능 중 하나입니다.

 

우리는 개발할 때마다 사용되는 패키지의 최신 버전이 무엇인지를 매번 확인하지 않지만, 최신 버전은 항상 업데이트가 이루어지고 있습니다.

 

즉, 버그 수정 및 신규 기능 추가 등을 별도의 버전 정보 변경 없이 자연스럽게 사용할 수 있도록 도움을 줍니다.

 

호환성이 깨지지 않는 범위 안에서 안전하게 버전을 관리해 주기 때문에 서로 다른 패키지들 간에 엮여 있는 버전 충돌을 유연하게 대처하고 수동 업데이트의 불편함을 해소할 수 있습니다.

 

 

 

[ npm ci ]

 

npm ci를 사용하는 가장 바람직한 시점은 CI 환경에서 사용할 때입니다.

 

Dockerfile 내부에서 build를 하거나, CI 파이프라인에서 build를 하는 행위를 할 때는 npm ci를 사용하는 것이 바람직합니다.

 

CI 환경에서는 우리가 로컬 개발을 하면서 실제 사용된 버전 정보들이 그대로 재현되는 것이 중요합니다.

 

npm install은 그대로 재현하는 것에 영향을 줄 수 있기 때문에, 버전 정보 유지가 필요할 때는 npm ci 사용을 다시 한번 권장드립니다.

 

 

 

 

 

 

 

이상으로 npm install과 npm ci의 비교, package-lock의 역할에 대해 간단하게 알아보는 시간이었습니다.

 

읽어주셔서 감사합니다.

 

 

 

728x90
반응형

댓글