Use npm ci in CI and release builds. It installs exactly what the lockfile records, refuses to change the lockfile, and exits with an error if package.json and the lockfile disagree. Plain npm install may update the lockfile, which lets a new version arrive without anyone reviewing it.
What the npm documentation says
The npm documentation for npm ci states:
- The project must have an existing package-lock.json or npm-shrinkwrap.json.
- If a node_modules folder exists, it is removed before the install starts.
- If the lockfile and package.json do not match, it exits with an error instead of updating the lock.
- It never writes to package.json or the lockfiles, so installs are essentially frozen.
Why that is safer
A malicious release usually reaches you through version drift. A range such as caret 1.2.0 lets npm choose 1.2.9 the next time it resolves. If 1.2.9 is the compromised release, a build that resolves fresh picks it up.
With npm ci, resolution already happened when someone ran npm install on a laptop, and the result went through code review as a lockfile change. CI only reproduces it.
When to use each
| Situation | Command | Reason |
|---|---|---|
| CI, release builds, container builds | npm ci | Reproducible and fails on drift |
| Adding or upgrading a dependency on purpose | npm install | You want the lockfile to change, then review the diff |
| Quick local experiment | Either | Low stakes |
What npm ci does not do
- It does not stop install scripts. The documentation does not describe a script difference, so set the ignore-scripts option if you want them off. See how to disable install scripts.
- It does not vet the pinned versions. If the lockfile already points at a bad release, npm ci installs it faithfully.
- It does not replace review. The lockfile diff is where review should happen. Reviewers should read added packages, version jumps and changed registry hosts.
Review lockfile changes properly
In a pull request that touches the lockfile, check these:
- New packages: do you recognize each name?
- Large version jumps in a single package.
- Changes to the resolved host or integrity fields.
- Packages that gained install scripts.
A gate can do the first pass on every pull request, so humans only read what is flagged. See also npm lockfile practices.
Click through it at your own pace, or open it full screen. Look at how a change is blocked or allowed by a rule.
Quick check
Does npm ci fail if I have no lockfile?
Yes. The documentation says the project must have a package-lock.json or npm-shrinkwrap.json.
Is npm ci slower?
It deletes node_modules first, so it reinstalls everything. Cache the npm download cache in CI to keep it quick.
Safeguard policies and gates can check dependency changes in pull requests. A free scan shows what your lockfile resolves to today.
Sources
Last reviewed: 19 October 2026.