Run npm ci in CI, not npm install, for locked dependency installs
npm ci installs exact versions from package-lock.json and fails if the lockfile is out of sync, making it ideal for CI pipelines.
03 Sept 2025, 10:39 UTC

The dependency drift problem
A test suite passes on your continuous integration server but fails on a teammate's laptop. The same repository, the same Node.js version, yet different behavior. Often the cause is dependency drift: package.json declares a semantic version range such as ^1.2.3, while package-lock.json - the lockfile that records exact resolved versions - pins a specific release. When CI runs npm install, it can resolve a newer patch version that satisfies the range, install it, and even rewrite the lockfile. Your local machine may still have the older version, so tests diverge.
The useful takeaway is to run npm ci in CI pipelines. It installs the exact versions recorded in package-lock.json, refuses to run if the lockfile is out of sync with package.json, and never modifies the lockfile. Use npm install locally when you are actively adding or upgrading dependencies.
How npm ci differs from npm install
npm ci stands for clean install. It removes the existing node_modules directory - the folder where installed packages live - then installs dependencies strictly from the committed lockfile. It ignores the semver ranges in package.json during installation and never writes to package-lock.json. If the lockfile is missing or does not match package.json, the command exits with an error. It requires npm 5.7.0 or later.
| Behavior | npm install | npm ci |
|---|---|---|
| Updates package-lock.json | May update it | Never modifies it |
| Handles node_modules | Updates existing tree | Removes and recreates it |
| Version source | Resolves ranges from package.json | Exact versions from lockfile |
| Best suited for | Local development | CI/CD automation |
| Fails when | Rarely | Lockfile missing or out of sync |
Worked example: run npm ci in CI
Run this command on a CI runner - such as GitHub Actions, GitLab CI, or Azure Pipelines - after checking out the repository. The runner needs read and write access to the working directory; no administrator privileges are required.
# Example GitHub Actions step
- name: Install dependencies
run: npm ciMeaningful placeholders and checks:
- Where to run: CI job shell, after checkout.
- Required permissions: Read/write to the repository checkout directory.
- Expected check 1: The command exits with code 0.
- Expected check 2:
node_modules/exists after the run. - Expected check 3:
git status --porcelain package-lock.jsonreturns no output, proving the lockfile was not changed.
Diagnostic commands you can add to the job:
npm --version
npm ci
git status --porcelain package-lock.jsonConfirm that npm --version reports 5.7.0 or higher. Do not assume the output; read it from the runner logs.
Relevant risks:
npm cideletesnode_modulesbefore installing, so it is not appropriate for a local folder where you want to preserve an existing, partially built tree.- It fails immediately if
package-lock.jsonis absent or out of sync withpackage.json. - It will not install a new dependency listed in
package.jsonunless that dependency is also in the lockfile.
Limitations and when not to use it
Avoid npm ci on your laptop while actively developing. If you add a dependency to package.json and then run npm ci, the command fails because the lockfile has not been updated. Instead, run npm install locally to update the lockfile, test the change, and commit both files. Without a committed lockfile, npm ci cannot run at all. It also offers no incremental install; every run wipes node_modules and starts over.
A practical way to verify the result after a CI run is to check the runner logs for a zero exit code, confirm node_modules is present, and confirm the lockfile shows no changes in git status. If any of those fail, the dependency state is not deterministic and should be fixed before merging.
Closing recommendation
Add npm ci to the install step of your CI pipeline and require package-lock.json to be committed. Tell your team to use npm install locally when adding or upgrading packages, then commit the updated lockfile. This combination keeps CI installations reproducible and prevents dependency drift from reaching production.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.