Skip to content

`npm install` vs `npm ci`

npm install resolves your dependencies. It reads package.json, works out a tree that satisfies it, installs that, and writes what it decided into package-lock.json. If a newer version satisfies the range in package.json, it may pick it, and the lock file changes.

npm ci does not resolve anything. It reads package-lock.json, deletes node_modules outright, and installs exactly what the lock says. It never writes the lock file. If package.json and the lock disagree, it refuses to run.

The ci is for continuous integration, which is where it was born. It is not “install, but the good one” — it is a different operation with a different promise.

npm install when you are changing what you depend on: adding a package, removing one, deliberately taking a newer version.

npm ci when you are reproducing a known-good tree: a build server, a container image, a fresh clone, a teammate’s machine, or your own machine when you want to be certain the problem is not your node_modules.

The daily case for most developers is npm install, because most days you are working on the project rather than reproducing it.

Ask one question: am I trying to change the dependency tree, or reproduce it?

If you cannot answer, this one usually settles it:

Would it be a problem if package-lock.json showed up in your next commit?

If yes — you are reproducing, and you want npm ci. If a lock file change would be the expected and intended result of what you are doing, you want npm install.

The build-server version of the question is shorter: a build that runs npm install can produce a different tree tomorrow than it did today, from the same commit. If that sentence bothers you, use npm ci.

npm ci fails and the message is about sync, not about you.

npm error `npm ci` can only install packages when your package.json and
package-lock.json are in sync.

This almost always means somebody edited package.json by hand and did not run npm install to update the lock. The fix is to run npm install once, locally, and commit the resulting lock file. The fix is not to switch the build to npm install, though that is what makes the error go away, which is why it happens.

npm install on a build server succeeds and lies. There is no error. The build is green. Months later a patch release of something four levels deep changes behaviour, and the failing build and the passing build have the same commit hash. This is the expensive one precisely because nothing ever presented as a failure.

npm ci deletes node_modules. Entirely, every time, on purpose. If you had something in there you were editing by hand, it is gone — and editing something in node_modules by hand was already going to end badly.

No lock file, no npm ci. It exits rather than generating one. On a project where package-lock.json is gitignored, this reads as npm ci being broken. The lock file being ignored is the actual bug.

In any project with a lock file:

Terminal window
git status --short package-lock.json # clean?
npm install
git status --short package-lock.json # still clean?
What you should see, and what it means

Usually clean both times: your lock already satisfies package.json and npm had nothing to change.

If it comes back modified, that is worth knowing — your lock file was not in sync with what package.json allows, and every npm install anyone runs has been quietly producing a slightly different tree. Commit it.

Three ways to take this further. Paste them as they are, or edit in your own project’s details — the context is what makes the answer worth having.

Verify the model. “I now think the difference between npm install and npm ci is that install can rewrite package-lock.json and ci never does, so ci is for reproducing a tree and install is for changing one. Is that the whole picture, or is there something that matters on a team of six that I am missing?”

Break it. “Show me a small, concrete example where a CI pipeline running npm install builds green for weeks and then fails with no code change. I want the actual sequence of events and what would appear in the build log, not just the general principle.”

Map around it. “I understand lock files well enough to choose between install and ci. What else about lock files bites teams — merge conflicts in them, transitive dependency updates, monorepos? Give me a short list and say which one I am most likely to hit first.”

npm install decides; npm ci obeys. Use the one whose promise you actually want.