npm install vs npm ci: when to use each with package-lock.json
Choose npm install for flexible local development and npm ci for reproducible CI builds. Compare lockfile handling, speed, and failure modes to avoid drift between environments.
10 Jan 2026, 06:39 UTC

The problem: same code, different dependency tree
You push code that works locally, CI installs a slightly newer transitive dependency, and tests fail. The root cause is usually how the dependency install is performed and whether package-lock.json is respected.
package-lock.json records the exact version of every package in the dependency tree, not just top-level ranges from package.json. Semver ranges in package.json allow a range of versions, e.g. ^1.2.3. The lockfile pins the resolved version. Deterministic installs require using that pin.
Decision and constraints
Choose npm install for iterative local development where you want flexibility to add, update, or explore dependencies and have the lockfile updated to match.
Choose npm ci for automated, reproducible environments where the lockfile must be the source of truth and any drift between package.json and package-lock.json is an error.
Constraints to keep in mind:
- npm ci requires a package-lock.json present at the root and will fail if it is missing.
- npm ci deletes the existing node_modules before installing, which is destructive for local workflows.
- npm install may update package-lock.json when the semver ranges in package.json allow a newer compatible version.
Options compared
| Option | Typical use | Lockfile behavior | node_modules handling | Failure mode |
|---|---|---|---|---|
| npm install | Local development, adding dependencies | May update package-lock.json if ranges allow newer versions | Preserves and augments existing modules | Installs succeed even if lockfile is out of sync |
| npm ci | CI, production deploys, reproducible builds | Requires lockfile, does not update it | Removes node_modules then installs from lockfile | Exits with error if lockfile missing or package.json and lockfile are not synchronized |
Trade-offs
Reproducibility vs flexibility
npm ci skips the dependency resolution algorithm used by npm install. It installs exactly what is recorded in package-lock.json. That makes builds faster and repeatable, and prevents accidental environment drift.
npm install performs resolution and can write a new lockfile. That is useful locally, but running it in CI can silently update the lockfile or pull a newer compatible version, leading to "it works on my machine" bugs.
Safety and destructiveness
Because npm ci removes node_modules first, it is unsuitable for iterative local development where you want incremental installs. It is designed for clean environments.
npm install is non-destructive for local work but does not enforce synchronization. If a teammate edits package.json without updating the lockfile, npm install will still succeed locally while npm ci will fail.
Concrete implementation
Local development workflow
Run from the project root with read access to the npm registry and write access to the working directory.
npm installExpected check: package-lock.json may change if new versions satisfy the semver ranges. Review the diff before committing.
Risk: committing an unintentionally updated lockfile can introduce changes to the whole team.
CI workflow
Run from the project root in a clean workspace. The lockfile must be checked into source control.
npm ciRequired permissions: read access to the registry, write access to node_modules. npm ci will delete any existing node_modules directory before installing.
Expected check: exit code zero and a populated node_modules that matches the lockfile. If package.json and package-lock.json are not synchronized, the command fails.
Limitation: npm ci does not support flags that modify package.json, such as --save or --save-dev. It is install-only.
Validation you can perform
- Run npm install after a dependency release and check if package-lock.json changes. A change indicates the semver range allowed a newer version.
- Attempt npm ci in a project without package-lock.json to verify it fails because the lockfile is required.
- Modify a version range in package.json without updating the lockfile, then run npm ci to confirm the synchronization check fails.
These checks illustrate the contract: npm install is permissive and may update the lockfile, npm ci is strict and requires the lockfile to be authoritative.
Limitations
npm ci assumes the lockfile is complete and correct. It will not recover from a missing or corrupted lockfile. It also does not install optional peer dependencies differently than npm install; behavior follows the lockfile.
For current npm versions, behavior can change. Verify the exact semantics in your npm version documentation and test in a non-production environment before changing pipeline steps.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.