npm ci

package-lock.json(またはnpm-shrinkwrap.json)に書かれた内容そのままを、クリーンな状態でインストールするnpmのサブコマンド。名前の通りCI環境向けに設計されており、npm installと違って「インストールの結果として依存関係が変わる」ことがない。

npm install との違い

npm install npm ci
lockfileの扱い 出発点として使い、必要なら書き換える 絶対に書き換えない(frozen)
package.json 更新されうる(npm i fooなど) 書き換えない
lockfile必須か 不要(無ければ生成する) 必須。無ければエラー
package.jsonとlockfileの不一致 解決してlockfileを更新 エラーで終了
既存のnode_modules 差分だけ更新 開始前に自動で丸ごと削除
個別パッケージ追加 npm i fooでできる できない

要するにnpm ciは「lockfileを唯一の入力とし、package.jsonのバージョン範囲(^1.2.3など)の解決を一切行わない」。依存解決フェーズをまるごとスキップするので、クリーンな環境でのインストールはnpm installより速い。

エラーで終了するのが効いてくる場面

package.jsonとlockfileが食い違っているとエラーになる、という挙動は地味に重要。誰かがpackage.jsonだけ手で編集してlockfileの更新を忘れた場合、npm installは黙って辻褄を合わせてしまうが、npm ciはCIで落ちて気づかせてくれる。

同じ発想は他のパッケージマネージャにもある。

  • pnpm: pnpm install --frozen-lockfile
  • Yarn v2+(Berry): yarn install --immutable (v1では--frozen-lockfileだったが非推奨になった)
  • Cargo: cargo build --locked — 「ロックファイルが存在しない」「Cargoがロックファイルを変更しようとした」のどちらでもエラー終了する(--frozen--locked --offlineと等価)

使い分け

  • 開発中npm install。パッケージを足したり、バージョン範囲を解決してlockfileを更新するのはこちら。
  • CI・本番ビルド・Dockerイメージnpm ci。lockfileの厳密なバージョンだけが入るので再現性がある。「ローカルでは動くがCIで壊れる」「CIでは通るが本番で壊れる」を防げる。

Dockerfileで使う場合、node_modulesを毎回消す挙動と、package*.jsonだけ先にCOPYしてレイヤキャッシュを効かせる定番パターンは相性がよい。

COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . .

--omit=devを付けると、全ライフサイクルスクリプトでNODE_ENVproductionに設定される。

関連

  • サプライチェーン攻撃の観点でも、lockfileで厳密にバージョンを固定してnpm ciで入れることは、意図しないバージョンの取り込みを減らす基本的な防御になる(ただしlockfile自体が汚染される攻撃は防げない)。
  • vlt (vōlt)のようなnpm互換パッケージマネージャも、npmと同じコマンド体系を持つ以上この使い分けの発想を引き継いでいる。

#npm #javascript #package-manager #ci

出典

作成日時: 2026-09-04 15:12 / 更新日時: 2026-09-04 15:12