package-lock.json and npm ci

npm · Essentials · 12 min

The lockfile is what makes 'works on my machine' work on every machine.

package-lock.json records the exact resolved tree: every package's version, where it was downloaded from (resolved) and its content hash (integrity). It is generated from package.json ranges plus the registry state at install time.

When it changes:

  • npm install (no arguments) keeps locked versions that still satisfy package.json, and only resolves what is new or no longer matches.
  • npm install pkg, npm update, npm uninstall change it deliberately.
  • npm ci never changes it. It deletes node_modules, installs exactly the lockfile, and fails if package.json and the lockfile disagree. Use it in CI, Docker builds and deploys.

Rules of thumb:

  • Commit the lockfile for applications and libraries alike (it does not affect consumers of a published library; npm ignores lockfiles inside dependencies).
  • Never delete it to "fix" an install: you silently upgrade hundreds of transitive packages at once.
  • Merge conflicts: resolve package.json first, then run npm install; npm can repair a lockfile with conflict markers. Commit the result.
  • Pass --omit=dev (or set NODE_ENV=production) for production images.

npm 12 removed npm shrinkwrap: npm-shrinkwrap.json is no longer honoured, so rename it to package-lock.json if you still have one.

Example

# Developer: add a dependency (updates package.json AND the lockfile)
npm install zod
git add package.json package-lock.json

# CI / Docker: exact, clean, reproducible
npm ci                     # fails if package.json and lockfile disagree
npm ci --omit=dev          # production install without devDependencies

# Merge conflict in package-lock.json:
git checkout --theirs package-lock.json   # or fix package.json conflicts first
npm install                               # npm rebuilds a consistent lockfile
git add package-lock.json

# Dockerfile pattern: copy manifests first for layer caching
#   COPY package.json package-lock.json ./
#   RUN npm ci --omit=dev
#   COPY . .

Remember

  • The lockfile pins every version, URL and integrity hash
  • npm ci installs exactly the lockfile and never modifies it
  • Commit it; never delete it to fix problems
  • Resolve lockfile conflicts by re-running npm install

Watch out for

  • Running npm install in CI, which can drift from the reviewed lockfile.

Check yourself

  1. What does `npm ci` do if package.json requires a range the lockfile does not satisfy?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.npmjs.com/cli/v11/commands/npm-ci