Back to all articles
npm install vs npm ci: The DEFINITIVE Guide!

npm install vs npm ci: The DEFINITIVE Guide!

Learn the crucial difference between npm install and npm ci and why using the right command can save your production build and CI/CD from catastrophic failures.

Human-architected research synthesized with the assistance of AI personas.
9 min read

โœจTL;DR / Executive Summary

Learn the crucial difference between npm install and npm ci and why using the right command can save your production build and CI/CD from catastrophic failures.

๐Ÿ’ก TL;DR (Too Long; Didn't Read)

  • Development: Use npm install (or npm i) to add, remove, or update packages. It can modify your package-lock.json.
  • Production/CI/CD: Use npm ci to install dependencies. It's faster, safer, and guarantees 100% reproducible builds using exactly the versions from package-lock.json. It never modifies your package.json or package-lock.json files.

๐Ÿ“Š Complete Comparison Table

Aspectnpm installnpm ci
SpeedAverage (10-30s)Fast (5-15s)
DeterministicโŒ Not guaranteedโœ… 100% reproducible
Modifies package.jsonโŒ NoโŒ No
Modifies package-lock.jsonโœ… YesโŒ Never
Requires package-lock.jsonโŒ Optionalโœ… Mandatory
Removes node_modules firstโŒ Noโœ… Yes (automatic)
Installs devDependenciesโœ… Yesโœ… Yes (can disable)
Accepts ranges (^, ~)โœ… YesโŒ No (exact only)
Adds new depsโœ… YesโŒ No
CacheUsesOptimized
Ideal usageLocal DevelopmentCI/CD & Production

๐Ÿ” Detailed Explanation

npm install (or npm i)

How it works:

bash
npm install # Internal steps: # 1. Reads package.json # 2. Reads package-lock.json (if exists) # 3. Resolves dependency tree # 4. Compares with existing node_modules # 5. Updates only what's necessary # 6. MAY modify package-lock.json # 7. Installs to node_modules

When to use:

โœ… First time in project:

bash
git clone https://github.com/your-project.git cd your-project npm install # Creates package-lock.json

โœ… Add new dependency:

bash
npm install lodash npm install --save-dev typescript

โœ… Update dependencies:

bash
npm install lodash@latest npm update

โœ… Active development:

bash
# Working locally npm install

Behavior with versions:

json
{ "dependencies": { "next": "^15.0.3" } }
bash
# If 15.0.5 was released: npm install # May install 15.0.5 (within range ^) # Updates package-lock.json with 15.0.5

npm ci (Clean Install)

How it works:

bash
npm ci # Internal steps: # 1. Checks if package-lock.json exists (REQUIRED) # 2. Removes node_modules COMPLETELY # 3. Installs EXACTLY the versions from lock # 4. NEVER modifies package-lock.json # 5. Fails if package.json and lock don't match # 6. Faster (optimized)

When to use:

โœ… CI/CD Pipelines (ALWAYS!):

yaml
# .github/workflows/ci.yml - name: Install dependencies run: npm ci # Guarantees reproducibility

โœ… Production builds (ALWAYS!):

bash
# On server git pull npm ci npm run build

โœ… Test environments:

bash
# Docker RUN npm ci --only=production

โœ… After git pull (RECOMMENDED):

bash
git pull origin main npm ci # Guarantees same versions as team

โœ… Clean problems:

bash
# Something broke? Clean everything npm ci # Removes node_modules and reinstalls

Behavior with versions:

json
// package.json { "dependencies": { "next": "^15.0.3" } } // package-lock.json { "next": { "version": "15.0.3" } }
bash
# Even if 15.0.5 exists: npm ci # Installs EXACTLY 15.0.3 (from lock)

๐ŸŽฎ Real-World Use Cases

Scenario 1: New developer on team

bash
# Day 1 - Clone project git clone https://github.com/company/project.git cd project # Option A: npm install (works) npm install # Option B: npm ci (BETTER - faster and exact) npm ci # Why npm ci? # - Faster # - Guarantees same versions as team # - Avoids "works on my machine"

Scenario 2: Add library

bash
# Need to add Express npm install express # npm ci DOES NOT work here! # npm ci doesn't add packages # After adding: git add package.json package-lock.json git commit -m "Add express" git push

Scenario 3: Pull Request Review

bash
# Download PR to test git fetch origin pull/123/head:pr-123 git checkout pr-123 # Use npm ci (guarantees exact PR deps) npm ci npm run build npm run test

Scenario 4: Automated deploy

bash
# Deploy script #!/bin/bash set -e echo "Deploying to production..." # 1. Pull latest git pull origin main # 2. Install dependencies (USE npm ci!) npm ci --only=production # Production only, faster # 3. Build npm run build # 4. Restart pm2 restart app

Scenario 5: Optimized Dockerfile

dockerfile
FROM node:20-alpine WORKDIR /app # Copy package files COPY package*.json ./ # USE npm ci in Docker! RUN npm ci --only=production # Copy code COPY . . # Build RUN npm run build CMD ["npm", "start"]

Scenario 6: GitHub Actions

yaml
name: CI on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup Node uses: actions/setup-node@v3 with: node-version: 20 cache: 'npm' # npm ci cache - name: Install dependencies run: npm ci # ALWAYS use npm ci here! - name: Type check run: npm run type-check - name: Lint run: npm run lint - name: Build run: npm run build - name: Test run: npm run test

โš ๏ธ Common Problems and Solutions

Problem 1: npm ci fails with "no such file package-lock.json"

bash
# Error: npm ci # Error: The package-lock.json file is missing # Cause: package-lock.json not committed # Solution: npm install # Creates lock git add package-lock.json git commit -m "Add package-lock.json"

Prevention:

gitignore
# DO NOT add this to .gitignore! # package-lock.json โŒ NEVER! # Always commit package-lock.json

Problem 2: "Works on my machine, but not in CI"

bash
# Dev: npm install # Installs version 15.0.5 # CI: npm ci # Installs version 15.0.3 (from old lock) # Problem: Different versions! # Solution: Always commit updated lock npm install # Updates lock git add package-lock.json git commit -m "Update dependencies" git push

Problem 3: npm ci is slow

bash
# npm ci should be faster, but it's slow? # Cause 1: Cache disabled npm config get cache # If empty, configure: npm config set cache ~/.npm --global # Cause 2: node_modules very large # npm ci removes before installing, may take time # Solution: Clean before rm -rf node_modules npm ci

Problem 4: Conflict between package.json and lock

bash
npm ci # Error: The package-lock.json was created for a different version # Cause: package.json changed but lock wasn't updated # Solution: npm install # Updates lock npm ci # Now works

๐ŸŽฏ Decision Rules

Use npm install when:

bash
# โœ… Add/remove packages npm install express npm uninstall lodash # โœ… Update dependencies npm update npm install react@latest # โœ… First setup (create lock) npm install # โœ… Active local development npm install

Use npm ci when:

bash
# โœ… CI/CD (ALWAYS!) npm ci # โœ… Production (ALWAYS!) npm ci --only=production # โœ… After cloning repository git clone ... && npm ci # โœ… After git pull git pull && npm ci # โœ… Automated tests npm ci && npm test # โœ… Docker builds RUN npm ci # โœ… Guarantee reproducibility npm ci

๐Ÿ“ˆ Performance Benchmark

Medium project (100 deps):

bash
# First installation npm install # ~25 seconds npm ci # ~15 seconds (40% faster!) # Installation with cache npm install # ~10 seconds npm ci # ~5 seconds (50% faster!) # With existing node_modules npm install # ~8 seconds (only updates) npm ci # ~15 seconds (removes all first)

Conclusion:

  • npm ci is faster when there's no node_modules
  • npm install can be faster if node_modules already exists

bash
# See what would be installed (without installing) npm install --dry-run # Install production only npm ci --only=production npm install --production # Force complete reinstall rm -rf node_modules package-lock.json npm install # See differences between package.json and lock npm ls --depth=0 # Verify lock integrity npm audit # Clean cache before installing npm cache clean --force npm ci

Add to your package.json:

json
{ "scripts": { "install:ci": "npm ci", "install:fresh": "rm -rf node_modules package-lock.json && npm install", "install:prod": "npm ci --only=production", "postinstall": "echo 'Dependencies installed successfully!'", "preinstall": "node -v && npm -v" } }

โœ… Final Checklist

For Development:

  • Use npm install to add deps
  • Commit package-lock.json always
  • Use npm ci after git pull
  • Don't ignore package-lock.json in git

For CI/CD:

  • ALWAYS use npm ci
  • Enable npm cache
  • Use --only=production if possible
  • Fail build if npm ci fails

For Production:

  • ALWAYS use npm ci --only=production
  • Never use npm install in production
  • Verify package-lock.json before deploy
  • Keep Node.js updated

๐Ÿ“š Visual Summary

npm installnpm ci
๐Ÿ“ Development๐Ÿš€ CI/CD
โž• Add depsโœ… Production
๐Ÿ”„ Update deps๐Ÿ”’ Reproduction
๐ŸŽจ Create lockโšก Performance

๐ŸŽ“ Executive Summary

For 99% of cases:

  1. Local development: npm install
  2. CI/CD and Production: npm ci
  3. After git pull: npm ci
  4. Add deps: npm install <package>
  5. Problems: rm -rf node_modules && npm ci

Golden rule:

  • If you will modify dependencies โ†’ npm install
  • If you will install dependencies โ†’ npm ci