npm install で実行される npm-script を ignore-scripts オプションで無視する

はじめに

npm 管理化のアプリをコンテナ化する際、docker build でエラーとなったのでメモがてら原因と解決策をまとめる。以下の条件に当てはまる場合発生するっぽい。

  • npm prepare が npm-scripts(package.json 内の script 内)に設定してある
  • husky など上記の npm prepare で使用しているライブラリが devDependencies にあり Dockerfile では devDependencies をインストールしないようにしている
    • npm install --omit=devnpm ci --omit=dev になっている場合 devDependencies 内のライブラリはインストールされない

実際のエラーはこんな感じ。

$ docker build -t react-app .
[+] Building 53.2s (8/10)
 => [internal] load build definition from Dockerfile                                                                  0.3s
 => => transferring dockerfile: 37B                                                                                   0.2s
 => [internal] load .dockerignore                                                                                     0.4s
 => => transferring context: 112B                                                                                     0.3s
 => [internal] load metadata for docker.io/library/node:16.16.0-bullseye-slim                                         1.1s
 => [1/6] FROM docker.io/library/node:16.16.0-bullseye-slim@sha256:cda7229eb72b7534396e7b58ba5b9f2454aee188317e058cb  0.0s
 => [internal] load build context                                                                                     0.8s
 => => transferring context: 4.58MB                                                                                   0.7s
 => CACHED [2/6] WORKDIR /usr/local/app                                                                               0.0s
 => [3/6] COPY ./package*.json ./                                                                                     0.2s
 => ERROR [4/6] RUN npm install  --production                                                                         50.2s
------
 > [4/6] RUN npm install --omit=dev:
#8 48.98
#8 48.98 > [email protected] prepare
#8 48.98 > husky install
#8 48.98
#8 48.99 sh: 1: husky: not found
#8 48.99 npm notice
#8 48.99 npm notice New minor version of npm available! 8.11.0 -> 8.16.0
#8 48.99 npm notice Changelog: <https://github.com/npm/cli/releases/tag/v8.16.0>
#8 48.99 npm notice Run `npm install -g [email protected]` to update!
#8 48.99 npm notice
#8 48.99 npm ERR! code 127
#8 48.99 npm ERR! path /usr/local/app
#8 48.99 npm ERR! command failed
#8 48.99 npm ERR! command sh -c husky install
#8 48.99
#8 48.99 npm ERR! A complete log of this run can be found in:
#8 48.99 npm ERR!     /root/.npm/_logs/2022-08-04T14_28_44_690Z-debug-0.log
------
executor failed running [/bin/sh -c npm install --omit=dev]: exit code: 127

原因

npm prepare が package.json の npm-script に定義されている場合、npm install をトリガに npm prepare が実行されてしまう。

If the package being installed contains a prepare script, its dependencies and devDependencies will be installed, and the prepare script will be run, before the package is packaged and installed. — ref. npm-install | npm Docs

package.json の一部を抜粋すると…

package.json
{
  "name": "sample-app",
  "scripts": {
    "build": "next build",
    "prepare": "husky install" // npm install をトリガに実行される
    // ... 略 ...
  },
  "devDependencies": {
    "husky": "^7.0.0",
    // ... 略 ...
  }
}

また Dockerfile では devDependencies をインストールしないようにしていると husky がインストールされず husky: not found となる。

Dockerfile
...

RUN npm install --omit=dev

...

対策

本記事のタイトル通りだが ignore-scripts というオプションがあったので、それを使うと回避できる!

If true, npm does not run scripts specified in package.json files.

Note that commands explicitly intended to run a particular script, such as npm start, npm stop, npm restart, npm test, and npm run-script will still run their intended script if ignore-scripts is set, but they will not run any pre- or post-scripts. - ref. npm-install | npm Docs

Dockerfile
...

- RUN npm install --omit=dev
+ RUN npm install --omit=dev --ignore-scripts

...

上記のオプションを入れると無事に docker build が成功する。

$ docker build -t sample-app --no-cache .
[+] Building 383.4s (11/11) FINISHED
 => [internal] load build definition from Dockerfile                                                                  0.1s
 => => transferring dockerfile: 313B                                                                                  0.1s
 => [internal] load .dockerignore                                                                                     0.0s
 => => transferring context: 2B                                                                                       0.0s
 => [internal] load metadata for docker.io/library/node:16.16.0-bullseye-slim                                         1.2s
 => [1/6] FROM docker.io/library/node:16.16.0-bullseye-slim@sha256:cda7229eb72b7534396e7b58ba5b9f2454aee188317e058cb  0.0s
 => [internal] load build context                                                                                     8.3s
 => => transferring context: 3.24MB                                                                                   7.9s
 => CACHED [2/6] WORKDIR /usr/local/app                                                                               0.0s
 => [3/6] COPY ./package*.json ./                                                                                     1.0s
 => [4/6] RUN npm install --omit=dev --ignore-scripts                                                              96.0s
 => [5/6] COPY ./ ./                                                                                                 18.0s
 => [6/6] RUN npm run build                                                                                         225.7s
 => exporting to image                                                                                               32.4s
 => => exporting layers                                                                                              32.3s
 => => writing image sha256:fccc09cf65b8e3a429c177b9b0023be30b8537049366e723b537ebd3b0b291c8                          0.0s
 => => naming to docker.io/library/sample-app                                                                               0.0s

Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them

まとめ

npm など日頃から使い慣れているツールだと思っていたが、こんなオプションがあるとは知らなかった! また、npm install の際に prepare が実行されるなどライフサイクルを知っていないと沼にハマりそうだなと思ったので、この記事をまとめてみた。

また今回は例として prepare を取り上げたが、postinstall, prepublish などの npm-script も該当すると思う。npm-scripts については下記のドキュメントを見るとだいたい分かるので是非参考にして欲しい。

scripts | npm Docs
How npm handles the "scripts" field
scripts | npm Docs favicon docs.npmjs.com
scripts | npm Docs

参考

npm-install | npm Docs
Install a package
npm-install | npm Docs favicon docs.npmjs.com
npm-install | npm Docs
npm の prepublish と prepare の変遷 - Qiita
2025-05-12 追記:いつ prepublishOnly なくなんの?→未定(たぶんやらない) Are changes to prepublishOnly and prepublish still planned? #8191 にある通りなんですけど、"ステップ4"...
npm の prepublish と prepare の変遷 - Qiita favicon qiita.com
npm の prepublish と prepare の変遷 - Qiita
ハンズオンNode.js
Node.jsの入門書。対象読者は、フロントエンド開発の知識はあってもサーバサイド開発は知らないエンジニアや、他言語の経験はあってもNode.jsは触ったことがないプログラマー。 本書ではターミナルのプロンプトにコマンドを入力してその反応を確認したり、簡単なスクリプトをNode.js環境で実行したりしながら、Node.jsプログラミングの基本からWebアプリケーションの開発、テスト、デプロイまでをハンズオン形式で学びます。また、コードの背景にある設計思想や、プログラムの挙動の仕組みについてもしっかり掘り下げます。 本書のゴールは、読者がNode.jsの全体像を掴み、業務レベルでのアプリケーション開発に対応可能な知識を身につけることです。
ハンズオンNode.js  favicon amazon.co.jp
ハンズオンNode.js
プロを目指す人のためのTypeScript入門 安全なコードの書き方から高度な型の使い方まで Software Design plus
こちらの書籍は 2023/08/30 紙版の4刷に対応するため更新をおこないました。 (概要) TypeScriptは、JavaScriptに静的型付けの機能を加えたオープンソースのプログラミング言語です。本書では、根幹となるJavaScriptの仕様・機能とともに、TypeScript独自の仕様・機能を解説します。TypeScriptの基礎知識はこれ一冊だけで学べます。 静的型付き言語は世にいくつもありますが、TypeScriptの型システムは他に類を見ない高い表現力を持っています。本書の読者が、型の有効性を理解しTypeScriptらしいコードを書けるようになるために、本書では、プログラムの安全性を高める基本的な型の扱い方から、TypeScriptの「高い表現力」の源となっているリテラル型・ユニオン型・keyof 型の扱い方まで幅広く取り上げます。また、わかりにくい機能や型安全を脅かす危険な機能についてもごまかさず、歴史的経緯や目的・用途を踏まえたうえで最善の扱い方を説明します。 章ごとに力試し問題を用意しており、理解の度合いを確認しながら学習を進められます。 (こんな方におすすめ) ・TypeScriptの初学者 ・JavaScriptの知識はないが、TypeScriptを学び始めたい人 (目次) 第1章 イントロダクション   1.1 TypeScriptとは   1.2 TypeScriptとJavaScriptとの関係   1.3 TypeScriptの開発環境 第2章 基本的な文法・基本的な型   2.1 文と式   2.2 変数の宣言と使用   2.3 プリミティブ型   2.4 演算子   2.5 基本的な制御構文   2.6 力試し 第3章 オブジェクトの基本とオブジェクトの型   3.1 オブジェクトとは   3.2 オブジェクトの型   3.3 部分型関係   3.4 型引数を持つ型   3.5 配列   3.6 分割代入   3.7 その他の組み込みオブジェクト   3.8 力試し 第4章 TypeScriptの関数   4.1 関数の作り方   4.2 関数の型   4.3 関数型の部分型関係   4.4 ジェネリクス   4.5 変数スコープと関数   4.6 力試し 第5章 TypeScriptのクラス   5.1 クラスの宣言と使用   5.2 クラスの型   5.3 クラスの継承   5.4 this   5.5 例外処理   5.6 力試し 第6章 高度な型   6.1 ユニオン型とインターセクション型   6.2 リテラル型   6.3 型の絞り込み   6.4 keyof型・lookup型   6.5 asによる型アサーション   6.6 any型とunknown型   6.7 さらに高度な型   6.8 力試し 第7章 TypeScriptのモジュールシステム   7.1 import宣言とexport宣言   7.2 Node.jsのモジュールシステム   7.3 DefinitelyTypedと@types   7.4 力試し 第8章 非同期処理   8.1 非同期処理とは   8.2 コールバックによる非同期処理の扱い   8.3 Promiseを使う   8.4 async/await構文   8.5 力試し 第9章 TypeScriptのコンパイラオプション   9.1 tsconfig.jsonによるコンパイラオプションの設定   9.2 チェックの厳しさに関わるオプション 続きを読む
プロを目指す人のためのTypeScript入門 安全なコードの書き方から高度な型の使い方まで Software Design plus  favicon amazon.co.jp
プロを目指す人のためのTypeScript入門 安全なコードの書き方から高度な型の使い方まで Software Design plus