Back to blog

npm install vs npm ci: Optimizing Dependency Installation in CI/CD Pipelines

Automating frontend build pipelines requires consistency. If your code compiles successfully on your local machine but fails on your Continuous Integration (CI) server (such as GitHub Actions or GitLab CI), the culprit is often how your dependencies were installed.

Many developers use the standard npm install command inside their automated build scripts.

However, npm install is designed for local development, not automation servers. For CI pipelines, npm provides a dedicated command: npm ci (Clean Install).

In this guide, we will compare npm install and npm ci, analyze how they handle lockfiles, and explain why npm ci accelerates build pipelines.

1. How npm install Works (Optimized for Local Dev)

The npm install command is designed to add packages and update environments dynamically.

  • Modifies Lockfiles: If you run npm install and your package.json specifies a flexible range (e.g., "lodash": "^4.17.0"), npm checks if a newer version exists (e.g., 4.17.21). If it does, npm downloads the newer version and overwrites your package-lock.json to match.
  • Iterative Upgrades: It scans your existing node_modules folder and attempts to download only the missing or outdated packages, avoiding full directory rewrites to save local download time.

While flexible for editing code locally, this behavior is dangerous for production deployments:

  1. Inconsistent Environments: If a dependency releases a buggy patch version, npm install on your CI server will pull the buggy patch, causing your production build to crash, even if your local tests passed.
  2. Slow Builds: Calculating and resolving package version trees during every pipeline run adds execution latency.

2. How npm ci Works (Deterministic Clean Install)

The npm ci command is designed specifically for automated testing, integration pipelines, and production deployments.

Here is what makes it different:

  • Strict Lockfile Enforcement: npm ci bypasses version resolution entirely. It reads your package-lock.json directly and installs the exact versions declared in it. If your package-lock.json is missing or does not match package.json, the build crashes immediately with an error rather than silently downloading arbitrary packages.
  • Never Modifies Lockfiles: It treats your lockfiles as read-only. It will never modify package.json or package-lock.json.
  • Clean node_modules Directory: Before downloading packages, npm ci automatically deletes your existing node_modules folder. This guarantees you are performing a completely clean compile, removing any cached files or untracked packages.
  • Speed: Because npm ci skips version tree calculations and dependency updates, it executes up to 2 times faster than npm install, saving precious pipeline minutes.

Configuration in CI Pipelines

Here is an example of integrating npm ci in a GitHub Actions workflow:

name: Node.js CI

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout Code
      uses: actions/checkout@v4

    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: '20'
        # Optional: enables automatic caching of npm registry files
        cache: 'npm'

    - name: Install Dependencies
      # Use npm ci instead of npm install
      run: npm ci

    - name: Run Tests
      run: npm test

    - name: Build Assets
      run: npm run build

Summary Comparison

Metric npm install npm ci
Primary Target Local development CI/CD pipelines, production
Lockfile write access Overwrites on version updates Read-only (never modifies lockfiles)
Requires Lockfile? No Yes (crashes if lockfile is missing)
Deletes node_modules? No (merges incrementally) Yes (always performs a clean reset)
Execution Speed Moderate Fast (skips version resolution)

Conclusion

To guarantee stable, reproducible frontend deployments, you must eliminate environmental variables. Always use npm install when you are actively writing code, updating packages, or working locally. However, for all automated environments—including linting, testing, and production building inside your CI/CD pipelines—switch to npm ci to accelerate build times and protect your system from dependency patch drift.