NPM CI vs NPM Install: Why Your CI/CD Pipeline is Breaking
We have all been there. A feature branch passes perfectly on your machine, but the moment the deployment pipeline runs, the build crashes.
The culprit is often a silent mismatch in your node modules. While npm install is your best friend during active development, using it in your deployment pipelines is an anti-pattern.
Here is exactly why you need to drop npm install in automated setups and start using npm ci.
The Core Difference
The main difference comes down to intent and predictability:
npm install(The Explorer): Designed for active local development. It readspackage.json, resolves version ranges, and can update or overwrite your lockfile.npm ci(The Executor): Short for “Clean Install”. It is built strictly for automated environments. It locks down your environment by ignoringpackage.jsonentirely and enforcing yourpackage-lock.jsonas the absolute truth.
💥 Real-World Example: The “Works on My Machine” Bug
Let’s look at how npm install can introduce hidden breaking changes in production.
The Scenario
Imagine you are building a React application. Your package.json lists a critical utility library with a caret range:
1
2
3
"dependencies": {
"awesome-utils": "^2.1.0"
}
Local Behavior
You run npm install. It installs version 2.1.0. Everything builds, passes your test suite, and you push your code to GitHub.
Pipeline Failure with npm install
Two hours later, the maintainers of awesome-utils release version 2.1.1, which accidentally introduces a breaking change.
If your GitHub Actions or Jenkins pipeline uses npm install, it checks the registry, notices 2.1.1 satisfies the ^2.1.0 requirement, and installs the broken update. Your production build fails, even though you didn’t change a line of code.
Pipeline Success with npm ci
If your pipeline uses npm ci instead, it ignores the new registry release. It looks directly at your committed package-lock.json file, sees that 2.1.0 was the frozen version, and installs exactly version 2.1.0. The build passes.
Key Benefits of npm ci
1. Striking Speed Improvements
Because npm ci does not spend time resolving semantic version ranges or querying the registry for updates, it downloads packages much faster. It can easily cut your build and deployment times in half.
2. Strict Validation Checkpoints
If a teammate adds a dependency to package.json but forgets to commit the updated package-lock.json, npm ci will instantly throw an error and halt the build. This stops out-of-sync configuration changes before they hit production.
3. Clean-State Isolation
When you run npm install, it leaves existing folders alone and updates incrementally. npm ci completely nukes your local node_modules folder before downloading anything. This guarantees that zero cached or leftover artifacts contaminate your clean build.
Quick Reference Summary
| Feature | npm install | npm ci |
|---|---|---|
| Ideal For | Local Workstations | CI/CD, Docker, Production |
| Source of Truth | package.json | package-lock.json |
| Can Modify Lock File? | Yes | No (Strictly Read-Only) |
Existing node_modules | Modifies incrementally | Deletes and starts fresh |
| Resolution Mismatch | Overwrites quietly | Throws fatal error |
When to Use Which
1
2
3
4
5
# Use this when adding packages or writing feature code locally:
npm install express
# Use this in your Dockerfiles, GitHub Actions, or deployment scripts:
npm ci
By switching to npm ci in your deployment workflows, you eliminate a massive category of random build failures and speed up your development cycle. Happy coding!