Building and installing an NPM package offline

Created On:

Recently I had to build an NPM package in an environment that did not have any network access 1. Normally if you run npm install --offline the build would normally fail with the following error.


This happens because NPM’s cache is not hydrated.

However it is possible to build the package if it has a package-lock.json and if all of the dependencies specified in the lock file are placed into NPM’s cache before the build. This blog post outlines how to do that so a NPM package can be built and installed in an offline environment.

Example package

For this blog post I have setup a very basic NPM package with a single dependency on the chalk package. The package is composed of the following files.

$ ls
index.js  package-lock.json  package.json

The contents of the example package.

The index.js contains:

#!/usr/bin/env node

import chalk from 'chalk';

console.log(chalk.magenta('Hello world!'));

The package.json contains:

  "name": "example-package",
  "private": true,
  "version": "0.0.0",
  "main": "index.js",
  "type": "module",
  "bin": "index.js",
  "dependencies": {
    "chalk": "5.2.0"

The package-lock.json was generated by running npm i --package-lock-only.

Building offline with Docker

Setting up an offline build environment with Docker is straight forward with the following Dockerfile.

# syntax-docker/dockerfile:1
FROM node:lts-hydrogen
COPY package.json /app
COPY index.js /app
COPY package-lock.json /app
RUN npm i --offline

Then running docker build --network none . triggers a build without network access and returns the ENOTCACHED error.

$ docker build --network none .
 => ERROR [6/6] RUN npm i --offline
 > [6/6] RUN npm i --offline:
#10 0.338 npm ERR! code ENOTCACHED
#10 0.339 npm ERR! request to failed: cache mode is 'only-if-cached' but no cached response is available.
#10 0.340

Hydrating the cache

To overcome the ENOTCACHED dependency the chalk-5.2.0.tgz file needs to be already placed into the cache before the build starts. Assuming chalk-5.2.0.tgz is already available then using npm cache add before npm install -offline fixes the problem. The Dockerfile can be changed to:

# syntax-docker/dockerfile:1
FROM node:lts-hydrogen

# Copy dependencies
COPY chalk-5.2.0.tgz /tmp/
RUN npm cache add chalk-5.2.0.tgz

COPY package.json /app
COPY index.js /app
COPY package-lock.json /app
RUN npm i --offline

By calling npm cache add on every dependency specified in the package-lock.json before the build, the build can occur offline.

$ docker build --no-cache --network none .
[+] Building 1.4s (14/14) FINISHED
 => [internal] load build definition from Dockerfile                                       0.0s
 => => transferring dockerfile: 38B                                                        0.0s
 => [internal] load .dockerignore                                                          0.0s
 => => transferring context: 2B                                                            0.0s
 => [internal] load metadata for                       0.1s
 => [1/9] FROM  0.0s
 => [internal] load build context                                                          0.0s
 => => transferring context: 133B                                                          0.0s
 => CACHED [2/9] WORKDIR /tmp/                                                             0.0s
 => [3/9] COPY chalk-5.2.0.tgz /tmp/                                                       0.0s
 => [4/9] RUN npm cache add chalk-5.2.0.tgz                                                0.3s
 => [5/9] WORKDIR /app                                                                     0.0s
 => [6/9] COPY package.json /app                                                           0.1s
 => [7/9] COPY index.js /app                                                               0.0s
 => [8/9] COPY package-lock.json /app                                                      0.0s
 => [9/9] RUN npm i --offline                                                              0.4s
 => exporting to image                                                                     0.2s
 => => exporting layers                                                                    0.2s
 => => writing image sha256:dcf3e70d01cabfd9a6065ac5990498865e623bce3a0c890ea8a9a5e424956  0.0s

Changing the entrypoint to /app/index.js shows the package was installed correctly.

docker run -it --entrypoint /app/index.js --rm sha256:ea603bac106fe180070b608537a2f34b7ba3ce6e31018331b06f7437f781e
Hello world!


It’s possible to build NPM packages in an offline environment so long as the package has a package-lock.json and all of the locked dependencies are added to the NPM cache prior to running npm install --offline. This enables building NPM packages in constrained environments using standard tooling.

  1. A nix build sandbox.↩︎