How can I use npm ci instead of npm install?

We’re working through some issues where some of our JS contributors are committing a package.json update for our deployed app without also updating package-lock.json. This presents problems when trying to create reproducible builds with deterministic dependencies. Most recently some of our builds have failed on Netlify but work fine locally (while silently overwriting package-lock.json and creating a dirty git tree). I want broken builds to break in CI, and automated builds should absolutely never overwrite the lockfile.

I’ve noticed that JS builds without a yarn.lock file automatically run npm install before turning over to the build command in my netlify.toml file. The yarn builds seem to run yarn install. Neither of these are correct. Both tools have ways to pull deps solely from their respective lockfiles and verify that the package.json is compatible with the locks. If the lockfiles don’t match package.json, the build will fail. For npm the correct invocation is npm ci and for yarn it appears to be yarn install --frozen-lockfile.

Is there any way to run npm ci instead of npm install, or otherwise have Netlify skip the install step so I can run npm ci myself without the automatic install step screwing up my checkout?

Hi Jeff & welcome to our community!

Our build system isn’t super flexible, but that pattern works for most folks and to change it would break builds on thousands of sites. Fortunately, you can work around it in a few ways:

  1. don’t have a package.json, package-lock.json or yarn.lock in the root of your repo (or in your base directory if you have one set). Then we won’t automatically run anything.
  2. you could instead “fake us out” and use something like $YARN_FLAGS set to --dry-run to make the auto-install a no-op, or maybe you want the --package-lock-only option to npm to create the lock, which you can then use? Not sure exactly what will best solve your use case. There’s a similar $NPM_FLAGS. You can set those in our UI, or in the netlify.toml config file.

Then you can run npm ci or yarn ci or whatever build command you like, in whatever way you like. Note that if you want to use anything other than npm - you’ll need to first INSTALL it- we have a VERY bare build environment by default, so you’ll need npm i yarn && yarn ci if you use yarn.

Note that unless you do some manual dependency caching at that point, you will have also opted out of that, since we run those installation commands with a special cache directory as shown here:

…so you might want to do some explicit cache management using a package like https://www.npmjs.com/package/cache-me-outside . That isn’t officially supported, but works well for many folks.

Sorry I don’t have a more direct way to accomplish your goal today. It’s interesting that this is the first we’ve heard of this need (not of the desire not to auto-run yarn/npm, but to use npm ci instead of npm install. Perhaps what you really need is a

Hello there, even I am looking for a similar solution. Currently Netlify fails to install packages from private registries (not private packages), and there doesn’t seem to be any way around it. Tried asking questions too, but no satisfactory answer was given. But, npm ci would have solved the problem

Ah, this is a very different problem, and there is a way around it which works well; this is how:

This is again related to private packages distributed via registry.npmjs.org. I was talking about private registries - not packages alone. For example, I have a private registry at r.privjs.com and I need to install a few packages hosted at r.privjs.com - but netlify always seems to request the package from registry.npmjs.org even though we clearly mentioned the registry URL in package-lock.json file

Sorry to be dense - I don’t use any private registries, so the distinction is lost on me. I’m suggesting that you use the example I mentioned above, and substitute //r.privjs.com for //registry.npmjs.org . Will that not work for your use case?

Um, that does not work because netlify (for some strange reason) always tries to install the packages from registry.npmjs.org even though private registry is specified in the package-lock.json file

Hey @prasannamestha,
It may be worth chiming in on this issue in our build-image repo:

It seems like npm ci was evaluated several years ago and decided against.

Another option could be creating a custom build plugin that could fetch from your private registry and make the package available during your build: