TT Lab
Get started
Learn Learning paths Courses

Air-Gapped Mirrors and a Private CA

Stand up an internal npm registry with verdaccio

Continue in TT Lab

Goal

Stand up an internal npm registry and fill it while connected, then confirm that installation still follows the lock file after the upstream connection is cut. Also measure yourself how the addresses in a lock file made outside get resolved.

Why it matters

Compressing the whole node_modules and carrying it in breaks when the node version differs and leaves no record of what went in. An internal registry accumulates packages one by one, and a lock file records "what, with which hash". When the two mesh, npm ci inside the air-gapped network builds the same tree as outside. This Pod can reach the internet (80/443), so it plays the download side, and the grader checks the air-gapped side with outbound HTTP(S) redirected to a closed proxy (127.0.0.1:9).

Steps

  1. In /opt/verdaccio, install verdaccio@6.1.6 (pin the version exactly, and the lock file must remain).
  2. Write /root/npm/config.yaml. The storage is /root/npm/storage, the uplink npmjs is https://registry.npmjs.org/, the pattern '**' is readable by everyone and uses npmjs as its proxy, the listen address is 127.0.0.1:4873, and the log is the file /root/npm/verdaccio.log.
  3. Add 127.0.0.1 npm.airgap.internal to /etc/hosts and start verdaccio with that configuration.
  4. In the .npmrc of the project /root/npm/app (it must have a package.json), write registry=http://npm.airgap.internal:4873/.
  5. Install chalk@4.1.2 in /root/npm/app so that the internal registry pulls from upstream and fills itself.
  6. Remove the proxy from the '**' pattern to cut the upstream connection, and start verdaccio again.
  7. Copy app's package.json and package-lock.json to /root/npm/app2 and run npm ci with a new cache (/root/npm/cache2).
  8. Create a lock file made with the outside registry in /root/npm/outlock/ (chalk 4.1.2), then, with the outside blocked, run npm ci against the internal registry once with the replace-registry-host default and once with never, and write the results to /root/npm/lockhost.txt as two lines, default=ok|fail and never=ok|fail.

Notes

Import item 1: verdaccio with a pinned version

In /opt/verdaccio, install verdaccio@6.1.6 (the lock file must remain).

When npm installs inside a directory, it creates that directory's node_modules and package-lock.json. A global install fails in this Pod because of permissions. Write the version exactly after the @.

Configure the proxy registry with an upstream (uplink)

In /root/npm/config.yaml, write the storage, the uplink, the pattern, the listen address, and the log file.

In uplinks, write the name and address of the upstream registry, and in the '**' pattern of packages, point to that name as proxy. listen is one line of host:port. For the log, give a path with type: file.

Serve the registry under the internal name

Add 127.0.0.1 npm.airgap.internal to /etc/hosts and start verdaccio with /root/npm/config.yaml.

verdaccio takes the configuration file with --config. Start it in the background so that it stays alive even if you close the terminal, and use the registry's ping address to check that it is alive.

Point to it with the project .npmrc

In the .npmrc of the project /root/npm/app (with a package.json), write registry=http://npm.airgap.internal:4873/.

The .npmrc in the project directory takes priority over the user and global settings. After writing it, use config get inside that directory to check which value npm actually uses.

Pull once to fill while connected

Install chalk@4.1.2 in /root/npm/app so that the internal registry pulls from upstream and keeps it in its storage.

The project's .npmrc points to the internal registry, so just install as usual. After installing, look at where the resolved address in the lock file points and what appeared in the registry's storage directory.

Cut the upstream connection to make an air-gapped registry

Remove the proxy from the '**' pattern and start verdaccio again.

A pattern without proxy serves only what is in the local storage. The configuration is read at startup, so you must stop the process and start it again. Request a package that is not in the storage and see whether you get a 404.

Install from the lock file with a new cache

Copy app's package.json, package-lock.json, and .npmrc to /root/npm/app2 and run npm ci with the new cache /root/npm/cache2.

npm ci requires a lock file and installs exactly from it. Giving a new cache directory means what was downloaded earlier cannot help, so you are testing whether the registry alone is enough.

How are the addresses in an outside lock file resolved?

Create a chalk 4.1.2 lock file with the outside registry in /root/npm/outlock/, then, with the outside blocked, run npm ci against the internal registry with the default and with --replace-registry-host=never, and write the results (ok or fail) as default= and never= in /root/npm/lockhost.txt.

What npm does when the host baked into resolved in the lock file is the default registry is decided by the replace-registry-host setting. For a fair comparison, run both with a new cache and a new directory, with the outside blocked. Judge the result by the command's exit code.