package-lock.json records the exact version of every installed package, so every install gets the same dependency tree. npm ci installs exactly what the lockfile says. npm install may change the lockfile.
package.json only lists version ranges. The lockfile records, for every installed package:
- the exact version
- the download URL
- the integrity hash
This includes transitive dependencies (the dependencies of your dependencies). Commit the lockfile. Then every developer and every CI run gets the same dependency tree. A new patch release of a sub-dependency cannot silently break a build.
npm install checks package.json against the lockfile and installs missing packages. It may update the lockfile if ranges changed or you add packages.
npm ci is for automated environments like CI. It:
- requires a lockfile
- deletes any existing
node_modules - installs exactly what the lockfile says
- never writes to the lockfile
- fails if the lockfile and
package.jsondon't match
This makes builds reproducible and usually faster.
- The lockfile pins exact versions and hashes, including transitive dependencies.
- Commit the lockfile so installs are the same everywhere.
npm installcan update the lockfile.npm ciinstalls exactly the lockfile, removesnode_modulesfirst, and fails on a mismatch.