LMX
BlogSelectedWorkSponsorsAbout
Command Palette

Search for a command to run...

Blog

Can you install packages without internet? Setting up a private npm repository on an intranet

LMXMar 12, 202620 min read#Verdaccio#npm

Introduce the use of NVM to manage multiple versions of Node in a physically isolated internal network environment, and set up a private npm repository with Verdaccio to achieve dependency caching and offline migration.

Introduction

Have you ever imagined a scenario where all your development computers are completely isolated from the network, and there is no frontend development environment installed on these computers, but your projects involve different tech stacks, such as Vue2, Vue3, React, etc.? At the same time, you might use package managers like npm, yarn, pnpm, or bun, and you also need to develop desktop applications in an internal network environment, similar to Electron or Tauri. Furthermore, your builds may require binary dependencies, and you might be using Vue3 to develop some business component libraries. To address these problems, you might have two options:

  • Option 1: Convert everything to npm and include node_modules along with the project;
  • Option 2: Use Verdaccio to set up a complete private npm registry that supports different tech stacks and package managers.

This leads to a scenario: how can we set up a frontend private npm repository that supports different tech stacks on a purely physically isolated, clean computer?

Below we will start using Verdaccio. After reading this article, you should have a certain understanding and familiarity with Verdaccio.

Preparation

Since we are working in a brand-new environment, taking Windows as an example, the first thing we need to install is Node. However, since our projects involve different versions of Vue and other dependencies, multiple Node versions are required for the development environment. In such cases, a Node version management tool is needed. Common options include nvm, fnm, volta, etc. Here, we will use NVM for installation (you can choose others as well). The following operations use nvm.

In other words, the steps below allow us to set up a frontend development environment on a clean, isolated internal network computer!

NVM Installation and Deployment

The following examples demonstrate the installation of NVM on Windows and Linux.

Windows Environment Installation

Download nvm-windows

Search for nvm-windows on GitHub, go to the project, click on Releases on the right, and download nvm-setup.exe or nvm-noinstall.zip. Github address: https://github.com/coreybutler/nvm-windows/releases

Download node js

Visit https://nodejs.org/dist/ to download the required version

Windows version list:

https://nodejs.org/dist/v12.22.12/node-v12.22.12-win-x64.zip
https://nodejs.org/dist/v14.21.3/node-v14.21.3-win-x64.zip
https://nodejs.org/dist/v16.20.2/node-v16.20.2-win-x64.zip
https://nodejs.org/dist/v18.19.0/node-v18.19.0-win-x64.zip
https://nodejs.org/dist/v20.11.0/node-v20.11.0-win-x64.zip

Installing nvm-windows

  • Click nvm-setup.exe to install
  • Extract nvm-noinstall.zip to the specified directory

For the installation directory, using C:\nvm as an example. Subsequent operations are done in this directory. If you need to change it, make the corresponding replacement.

Remember the two paths when installing:

  • nvm installation path: C:\nvm
  • Node.js symlink path: C:\Program Files\nodejs

Manually Adding Node.js Versions

Extract the downloaded Node.js manual package to the nvm installation directory:

Steps:

# 1. 解压 node-v12.22.12-win-x64.zip
# 2. 将解压后的文件夹重命名为 v12.22.12
# 3. 将 v12.22.12 文件夹移动到 C:\nvm\ 目录下
# 4. 重复以上步骤处理其他版本
# 目录结构应该是这样
C:\nvm\
  ├─ v12.22.12\
  │   ├─ node.exe
  │   ├─ npm
  │   └─ ...
  ├─ v14.21.3\
  ├─ v16.20.2\
  ├─ v18.19.0\
  └─ v20.11.0\

Modify settings.txt

Edit C:\nvm\settings.txt and make the following changes, and modify the corresponding root and path:

root: C:\nvm
path: C:\Program Files\nodejs
arch: 64
proxy: none

Configure Environment Variables

Make sure the following environment variables are set:

NVM_HOME = C:\nvm
NVM_SYMLINK = C:\Program Files\nodejs

Path 中添加:
%NVM_HOME%
%NVM_SYMLINK%

Linux Environment Installation

Download nvm

Search nvm on GitHub, enter the project, click on Releases on the right, download the corresponding version, and unzip it.

GitHub address: https://github.com/nvm-sh/nvm

Download node js

Visit https://nodejs.org/dist/ to download the required version

Linux version list:

https://nodejs.org/dist/v12.22.12/node-v12.22.12-linux-x64.tar.gz
https://nodejs.org/dist/v14.21.3/node-v14.21.3-linux-x64.tar.gz

...

When downloading the Node installation package needed on Linux, you need to pay attention to the architecture version; ARM and x64 are different, just be aware of this.

Install nvm

Steps:

# 以某个版本为例
wget https://github.com/nvm-sh/nvm/archive/refs/tags/v0.39.0.tar.gz
tar -xzf v0.39.0.tar.gz
mv nvm-0.39.0 .nvm

Configure Environment Variables

Edit ~/.bashrc or ~/.zshrc:

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"

Effective configuration:

source ~/.bashrc  # 或 source ~/.zshrc

Manually add Node.js version

# 创建版本目录
mkdir -p ~/.nvm/versions/node

# 解压并移动 Node.js
tar -xzf node-v12.22.12-linux-x64.tar.gz
mv node-v12.22.12-linux-x64 ~/.nvm/versions/node/v12.22.12

# 重复以上步骤处理其他版本

Directory Structure:

~/.nvm/versions/node/
  ├─ v12.22.12/
  ├─ v14.21.3/
  ├─ v16.20.2/
  ├─ v18.19.0/
  └─ v20.11.0/

NVM Verification and Usage

Verify Installation

# 重新加载环境
source ~/.bashrc

# 验证NVM
nvm --version

# 查看已安装的Node.js版本
nvm list

# 使用特定版本
nvm use 18.17.1

# 验证Node.js和npm
node --version
npm --version

# 测试npm基本功能
npm config list

Instructions for Use

# 切换Node.js版本
nvm use 16.20.2    # 切换到16版本
nvm use 18.17.1    # 切换到18版本
nvm use 20.5.1     # 切换到20版本

# 设置默认版本
nvm alias default 18.17.1

# 查看当前使用的版本
nvm current

# 查看所有可用版本
nvm list

# 在特定版本下运行命令
nvm exec 16.20.2 node --version

# 显示可用命令
nvm --help

# 卸载指定版本(删除文件夹)
nvm uninstall 12.22.12

Because we are doing an offline installation, all nvm install version are actually unavailable. The above steps are just manually implementing this command.

Common NVM Problems

Linux cannot find the nvm command

# 手动加载NVM
source ~/.nvm/nvm.sh

# 或者重新加载bashrc
source ~/.bashrc

Node command is invalid after switching versions

# Windows: 重新打开命令行窗口
# Linux/Mac: 检查 PATH 环境变量
echo $PATH

# 手动设置PATH
export PATH="$NVM_DIR/versions/node/v18.17.1/bin:$PATH"

npm Global Packages Missing

This is a fairly common issue:

  • Each node version has its own global package directory
  • After switching versions, global packages need to be reinstalled

Unable to Switch Node Versions

# 检查版本目录是否正确
ls -la ~/.nvm/versions/node/

# 手动设置PATH
export PATH="$NVM_DIR/versions/node/v18.17.1/bin:$PATH"

Permission Issues

# 修复NVM目录权限
chmod -R 755 ~/.nvm
chmod +x ~/.nvm/nvm.sh

NVM Uninstallation

Windows Uninstallation

Simply delete the installation directory and environment variables.

Linux Uninstallation

If you need to completely uninstall:

# 删除NVM目录
rm -rf ~/.nvm

# 从.bashrc中移除NVM配置
sed -i '/# NVM配置/,/# 加载nvm bash补全/d' ~/.bashrc

# 重新加载shell配置
source ~/.bashrc

The above operations are equivalent to installing Node and Node's version manager on the internal network machine. This is the first step and also a prerequisite. Next, we will move on to the formal setup of Verdaccio.

Verdaccio Installation and Deployment

First, consider this question: Our Verdaccio is installed globally via npm, and Verdaccio is published to npm js, but our internal network environment cannot access npm js. What should we do?

The following example explains in detail what we need to do using a Windows system:

  • Step 1: Install Verdaccio on a machine with internet access
  • Step 2: Use Verdaccio to cache the required dependencies, including both project dependencies and global dependencies (such as pnpm)
  • Step 3: Transfer Verdaccio itself and all cached dependencies to internal machines
  • Step 4: Dynamically update the dependencies in Verdaccio's storage and the config.yaml according to needs
  • Step 5: Transfer the updated storage dependencies and config.yaml to the internal machine for merging

Repeating the above steps can achieve private deployment of dependencies in most scenarios (though in certain special cases it may not be applicable, which will be explained later in missing content).

Windows Environment Installation and Deployment

Verdaccio Installation

We have already installed Node above, so we can directly use npm on the online machine to install it globally.

# 如果担心有问题,可以使用管理员权限安装
npm install -g verdaccio

# 其他包管理器的安装方式
yarn global add verdaccio
pnpm install -g verdaccio

Just wait for the installation to complete.

After the installation is complete, execute:

verdaccio --version  # 验证是否安装成功

Then proceed to start up. You need to pay attention to the startup information after starting up, as this is useful:

C:\Users\LMX>verdaccio
info --- config file  - C:\Users\LMX\AppData\Roaming\verdaccio\config.yaml
info --- plugin @verdaccio/local-storage successfully loaded (storage)
info --- using htpasswd file: C:\Users\LMX\AppData\Roaming\verdaccio\htpasswd
info --- plugin verdaccio-htpasswd successfully loaded (authentication)
info --- plugin verdaccio-audit successfully loaded (middleware)
info --- plugin @verdaccio/ui-theme successfully loaded (theme)
warn --- http address - http://localhost:4873/ - verdaccio/6.2.1

If we do not focus on managing private packages, we need to pay attention to two places:

  • C:\Users\LMX\AppData\Roaming\verdaccio\ (the path where the configuration file and storage are located)
  • http://localhost:4873/ (visualization address; later we will make all dependencies display simply at this address)

Register Installation Source

After Verdaccio is installed, a relatively important next step is to register the registries of various package managers so that Verdaccio can properly cache dependencies.

Set a global registry for all projects:

npm set config registry http://localhost:4873 # 注册地址就是上面的verdaccio的访问地址

yarn config set registry http://localhost:4873

You can also register a dependency individually:

npm install lodash --registry http://localhost:4873

Other commonly used commands:

# 终端窗口打开项目的根目录
npm set registry http://localhost:4873/ --location project

# 单次依赖安装指定注册中心,以lodash为例
npm install lodash --registry http://localhost:4873

The above command is actually equivalent to adding a registration center under your project .npmrc. You can also directly modify .npmrc:

registry=http://localhost:4873/

But now many Vue 3 projects use multi-package management with monorepo, so we certainly can't avoid pnpm. Actually, there's a bit of extended knowledge here.

For a pure intranet setup, globally installing pnpm can actually be implemented in two ways.

  • Approach 1: Use Verdaccio to cache pnpm dependencies
  • Approach 2: Use npm's pack command to package pnpm

Approach 2 will be explained in later chapters. For now, let's practice with Approach 1.

Start Verdaccio following the steps above and specify pnpm's registry:

verdaccio
npm  install  pnpm  --registry http://localhost:4873

At this time, the Verdaccio window will display the information of pnpm being installed from the cache:

C:\Users\LMX>verdaccio
warn --- This is a deprecated method, please use runServer instead
info --- config file  - C:\Users\LMX\AppData\Roaming\verdaccio\config.yaml
info --- plugin @verdaccio/local-storage successfully loaded (storage)
info --- using htpasswd file: C:\Users\LMX\AppData\Roaming\verdaccio\htpasswd
info --- plugin verdaccio-htpasswd successfully loaded (authentication)
info --- plugin verdaccio-audit successfully loaded (middleware)
info --- plugin @verdaccio/ui-theme successfully loaded (theme)
warn --- http address - http://localhost:4873/ - verdaccio/6.2.1
info <-- 127.0.0.1 requested 'GET /pnpm'
http <-- 200, user: null(127.0.0.1), req: 'GET /pnpm', bytes: 0/0
info --- making request: 'GET https://registry.npmjs.org/pnpm'
http --- 200, req: 'GET https://registry.npmjs.org/pnpm' (streaming)
http --- 200, req: 'GET https://registry.npmjs.org/pnpm', bytes: 0/5275788
http <-- 200, user: null(127.0.0.1), req: 'GET /pnpm', bytes: 0/779825
info <-- 127.0.0.1 requested 'POST /-/npm/v1/security/advisories/bulk'
http <-- 200, user: null(127.0.0.1), req: 'POST /-/npm/v1/security/advisories/bulk', bytes: 40/0
info <-- 127.0.0.1 requested 'GET /pnpm/-/pnpm-10.32.1.tgz'
http <-- 200, user: null(127.0.0.1), req: 'GET /pnpm/-/pnpm-10.32.1.tgz', bytes: 0/0
info --- making request: 'GET https://registry.npmjs.org/pnpm/-/pnpm-10.32.1.tgz'
http <-- 200, user: null(127.0.0.1), req: 'POST /-/npm/v1/security/advisories/bulk', bytes: 40/2
http <-- 200, user: null(127.0.0.1), req: 'GET /pnpm/-/pnpm-10.32.1.tgz', bytes: 0/4524746

Afterwards, go to Verdaccio's storage directory to check whether it has been properly cached.

Normally, the pnpm folder has two files:

package.json
pnpm-10.32.1.tgz

Normally, a tgz package has a size, so if it is 0k, you need to be careful. Of course, it’s impossible for pnpm to be the only dependency; it is a dependency tree, and this is just an example to illustrate the point.

Points to Note:

When we use Verdaccio for dependency caching, we may encounter situations where the dependency is not cached. This situation does exist, and the general approach to handle it is as follows:

  • Clear the cache and install again
  • Restart Verdaccio and install again
  • If conditions allow, delete the entire Verdaccio cache and install again

The related commands are as follows:

npm  cache clean --force
pnpm  store  prune

Configuration File

After configuring the registration sources above, we need to understand two files: config.yaml and .verdaccio-db.json. These two files are helpful for our subsequent operations, and we need to understand them.

config.yaml

This file is in the same directory as storage. The configuration content of the file is as follows, and we need to understand some of the configuration items in it:

# 缓存依赖的存储位置
storage: ./storage

# 监听所有网卡(让其他机器能访问)
listen: 0.0.0.0:4873

web:
  title: Verdaccio
auth:
  htpasswd:
    file: ./htpasswd

# 内网机器的一定要关闭uplinks
uplinks:
  npmjs:
    url: https://registry.npmjs.org/
    cache: true

packages:
  "@*/*":
    access: $all
    publish: $authenticated
    unpublish: $authenticated
    proxy: npmjs
  "**":
    access: $all
    publish: $authenticated
    unpublish: $authenticated
    proxy: npmjs #  内网机绝对不能有这行
server:
  keepAliveTimeout: 60
middlewares:
  audit:
    enabled: true
log:
  type: stdout
  format: pretty
  level: http
i18n:
  web: en-US

Official statement of cache attributes: Setting cache to false can help save your hard drive space. This will avoid storing tarballs, but it will retain metadata in the folder.

Actually, the official explanation is a bit obscure. It can actually be understood like this:

  • cache: true (default); tarballs pulled from upstream will be cached in the storage directory, and next time requests will return directly from local.
  • cache: false; tarballs will not be written to disk, but the package metadata (package.json, version lists, etc.) will still be cached, and each request for a tarball will fetch it from upstream.

To explain further, what is tarball? Answer: it is the tgz file of the dependency package.

Misconfiguration of this setting is also an important reason that can prevent caching.

Further explanation:

⚠️ Key point: Internal network machine: In a physically isolated environment, you must remove proxy: npmjs, otherwise every package installation will get stuck trying to connect to the external network.

⚠️ Key point: External network machine: For machines that can access the internet, do not remove the proxy, but cache should either not be set or be set to true.

.verdaccio-db.json

How do we understand .verdaccio-db.json?

The official statement is:

The miniature database is used to store private packages published by users. This database is based on JSON files and contains a list of published private packages as well as secret tokens used for token signing. The database is automatically created on the first application startup.

The contents of the file are as follows:

{ "list": [], "secret": "KzZPQifqvCrV7HBMyVPb1C9+FdWteKqe" }

I have released secret (token key) here. Normally, this should not be exposed and is only for demonstration purposes.

Start verdaccio and visit: http://localhost:4873. We will find nothing, because our list: [] is empty.

Only when we publish npm packages to our private server (verdaccio will actively write to the database file only when publishing private packages), list will be updated.

But if we want verdaccio to act as a private npm, we want to append the packages we have already cached into list. Therefore, we need to write a JS file sync-verdaccio-db.js to update it:

The main idea is to access: http://localhost:4873/-/all, to get all dependency data.

const fs = require("fs");
const path = require("path");
const http = require("http");

const VERDACCIO_URL = "http://localhost:4873";
// 根据实际路径调整
const DB_PATH = path.join(__dirname, "storage", ".verdaccio-db.json");

/**
 * 获取所有包名(从/-/all)
 * @returns
 */
function fetchPackages() {
  return new Promise((resolve, reject) => {
    http
      .get(`${VERDACCIO_URL}/-/all`, (res) => {
        let data = "";
        res.on("data", (chunk) => (data += chunk));
        res.on("end", () => {
          try {
            const json = JSON.parse(data);
            resolve(Object.keys(json)); // 返回包名数组
          } catch (e) {
            reject(e);
          }
        });
      })
      .on("error", reject);
  });
}

/**
 * 更新数据库文件
 * @param {*} packages
 */
function updateDb(packages) {
  let db;
  try {
    db = JSON.parse(fs.readFileSync(DB_PATH, "utf8"));
  } catch (e) {
    // 文件不存在或损坏,创建一个新的
    db = {
      list: [],
      secret: require("crypto").randomBytes(16).toString("hex"),
    };
  }
  // 去重并排序(可选)
  db.list = [...new Set(packages)].sort();
  fs.writeFileSync(DB_PATH, JSON.stringify(db, null, 2));
  console.log(
    `[${new Date().toISOString()}] Database updated with ${
      db.list.length
    } packages.`,
  );
}

/**
 * 主函数
 */
async function main() {
  try {
    const packages = await fetchPackages();
    updateDb(packages);
  } catch (err) {
    console.error("Sync failed:", err.message);
  }
}

main();

Note: Our approach above is just to display which dependencies are installed. Dependencies have a dependency tree, which is understood by default and will not be explained in detail!

First start verdaccio, then in the same directory as storage, use node to execute the sync-verdaccio-db.js file, as follows:

node  .sync-verdaccio-db.js

⚠️ After completion, restart Verdaccio, and then open http://localhost:4873 in the browser to view the cached dependencies.

Create User

Also briefly explain how to register and log in users for Verdaccio, as it might be useful later, but it is not the focus of this article.

When using npm version 8 or lower, both adduser or login can be used to create a user and log in at the same time.

npm adduser --registry http://localhost:4873

After npm@9, these two commands work separately:

npm login --registry http://localhost:4873
npm adduser --registry http://localhost:4873

By default, both commands rely on web login. Adding --auth-type=legacy allows you to use the previous login method.

Intranet Deployment

After completing the above operations, the next step is how to deploy the entire Verdaccio to an intranet machine. As long as you move Verdaccio to the intranet machine and start it, afterwards you only need to update the storage and .verdaccio-db.json to achieve dependency updates.

Actually, there are two approaches:

  • Approach 1: Use npm pack to package Verdaccio, then use npm to install it globally on the intranet machine.
  • Approach 2: Directly copy the Verdaccio installed on the machine with internet access to the intranet machine (personally, I think this is the most reliable way).

Approach 1 seems more standardized, but may encounter unexpected issues. Approach 2 has a higher success rate in the Windows environment. We will introduce Approach 2 first, and then explain Approach 1.

Since it has already been installed globally above, we need to check the global installation path.

npm  config  get  prefix

The output result is as follows:

C:\Users\LMX>npm  config  get  prefix

C:\nvm4w\nodejs  # 这个路径是你自己的,每个人的安装路径都不一样

Enter the installation directory and copy the relevant files to the same directory of npm on the internal network machine.

The files that need to be copied include (verdaccio, verdaccio.cmd, verdaccio.ps1, and the node_modules/verdaccio folder):

1.png

2.png

Finally, verify on the internal network machine whether Verdaccio is successful.

verdaccio --version

After Verdaccio is successfully installed, simply start Verdaccio and copy the previously saved storage and config.yaml to the Verdaccio cache directory on the internal network machine.

Although Approach Two is a bit unconventional, it is actually the method with a higher success rate when attempting on Windows.

A brief explanation of why Approach Two is simple and straightforward:

When we install dependencies, it’s not just a single dependency, but a complex dependency tree. By directly copying node_modules/verdaccio, we essentially skip the process of searching for and installing the complex dependency tree. This operation is essentially an environment dependency migration process.

Now let’s explain the seemingly formal Approach One:

For Approach One to succeed, a very important point is that you not only need to npm pack Verdaccio, but also npm pack each sub-dependency required by Verdaccio. This is extremely tedious and prone to errors.

So can we just package node_modules/verdaccio directly, similar to the idea of Approach Two? In that case, we basically return to Approach Two, which we won’t elaborate further.

However, combining Approach One and Approach Two, we can think of a more optimal engineering practice:

Use Verdaccio to bootstrap Verdaccio

First, deploy Verdaccio on the internal network machine using Approach Two. Then, on the external network, start Verdaccio and let it cache Verdaccio itself along with all its dependencies, and then copy the storage data to the internal network Verdaccio cache. Other internal network machines can directly install Verdaccio via the already installed Verdaccio on the internal machine.

All the installations mentioned above are illustrated using Windows as an example. So when our development environment is Linux, what should we do?

Generally speaking, most front-end development environments are Windows, which is why the Windows part is our main concern!

This also happens to illustrate how to migrate pnpm via packaging rather than Verdaccio caching.

Linux Environment Installation and Deployment

A brief explanation for the Linux environment for general understanding; it is not the focus of this article. Verdaccio Installation

# 安装 Verdaccio 和 pnpm
npm install -g verdaccio pnpm

# 验证安装
verdaccio --version
pnpm --version

Confirm Installation Structure

PREFIX=$(npm config get prefix)

# 查看可执行文件类型(确认是否为软链接)
ls -la $PREFIX/bin/ | grep -E "verdaccio|pnpm"
# 示例输出:
# lrwxrwxrwx ... verdaccio -> ../lib/node_modules/verdaccio/bin/verdaccio
# lrwxrwxrwx ... pnpm -> ../lib/node_modules/pnpm/bin/pnpm.cjs

# 查看模块目录
ls $PREFIX/lib/node_modules/ | grep -E "verdaccio|pnpm"

Start verdaccio and download project dependencies

# 后台启动 Verdaccio
verdaccio &

# 配置 pnpm 使用 Verdaccio
pnpm config set registry http://localhost:4873/

# 安装所有项目依赖(会自动缓存到 Verdaccio)
cd /path/to/project1
pnpm install

cd /path/to/project2
pnpm install

# 重复所有项目...

# 完成后停止 Verdaccio
pkill -f verdaccio

Package all files

PREFIX=$(npm config get prefix)
mkdir -p ~/verdaccio-offline-package
cd ~/verdaccio-offline-package

# 打包完整模块目录(含所有子依赖,这是关键)
tar -czf verdaccio-full.tar.gz -C $PREFIX/lib/node_modules verdaccio
tar -czf pnpm-full.tar.gz -C $PREFIX/lib/node_modules pnpm

# 记录软链接目标路径(供内网还原使用)
VERDACCIO_LINK=$(readlink $PREFIX/bin/verdaccio)
PNPM_LINK=$(readlink $PREFIX/bin/pnpm)
PNPX_LINK=$(readlink $PREFIX/bin/pnpx 2>/dev/null || echo "")

cat > link-targets.txt << EOF
VERDACCIO_LINK=$VERDACCIO_LINK
PNPM_LINK=$PNPM_LINK
PNPX_LINK=$PNPX_LINK
EOF

echo "软链接信息已记录:"
cat link-targets.txt

# 打包 verdaccio 配置和依赖缓存
tar -czf verdaccio-data.tar.gz -C ~ .config/verdaccio

# 查看打包结果
ls -lh
# 应该看到:
# verdaccio-full.tar.gz    # Verdaccio 完整模块
# pnpm-full.tar.gz         # pnpm 完整模块
# link-targets.txt         # 软链接记录
# verdaccio-data.tar.gz    # 配置和依赖缓存

du -sh .

Intranet Decompression Deployment

cd /tmp
tar -xzf verdaccio-offline-linux.tar.gz
cd verdaccio-offline-package

# 查看文件
ls -lh

Configure npm user global installation directory

mkdir -p ~/.npm-global
npm config set prefix ~/.npm-global

echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc

# 确认配置
npm config get prefix

Restore Module File

cd /tmp/verdaccio-offline-package
PREFIX=$(npm config get prefix)

# 确保目标目录存在
mkdir -p $PREFIX/lib/node_modules
mkdir -p $PREFIX/bin

# 还原完整模块(含所有子依赖)
tar -xzf verdaccio-full.tar.gz -C $PREFIX/lib/node_modules/
tar -xzf pnpm-full.tar.gz -C $PREFIX/lib/node_modules/

# 验证模块已还原
ls $PREFIX/lib/node_modules/ | grep -E "verdaccio|pnpm"

Restore Symbolic Link

PREFIX=$(npm config get prefix)

# 读取外网记录的软链接目标
source /tmp/verdaccio-offline-package/link-targets.txt

# 建立软链接
ln -sf $PREFIX/lib/node_modules/$VERDACCIO_LINK $PREFIX/bin/verdaccio
ln -sf $PREFIX/lib/node_modules/$PNPM_LINK $PREFIX/bin/pnpm

# pnpx 可选
if [ -n "$PNPX_LINK" ]; then
    ln -sf $PREFIX/lib/node_modules/$PNPX_LINK $PREFIX/bin/pnpx
fi

# 验证软链接
ls -la $PREFIX/bin/ | grep -E "verdaccio|pnpm"

Restore Verdaccio Data

tar -xzf /tmp/verdaccio-offline-package/verdaccio-data.tar.gz -C ~

# 验证
ls -la ~/.config/verdaccio/
ls ~/.config/verdaccio/storage/

Set up systemd service (optional)

PREFIX=$(npm config get prefix)

sudo tee /etc/systemd/system/verdaccio.service > /dev/null << EOF
[Unit]
Description=Verdaccio Private NPM Registry
After=network.target

[Service]
Type=simple
User=$USER
WorkingDirectory=$HOME
Environment=PATH=$PREFIX/bin:$HOME/nodejs/bin:/usr/local/bin:/usr/bin:/bin
ExecStart=$PREFIX/bin/verdaccio
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl start verdaccio
sudo systemctl enable verdaccio

# 查看状态
sudo systemctl status verdaccio

# 查看日志
sudo journalctl -u verdaccio -f

Precautions

When caching dependencies with verdaccio, the situation where only the package.json file in the dependency package is missing the corresponding .tgz package

This situation is the most common problem when caching dependencies in an online environment using verdaccio to set up a private npm.

The solution approach is to use pnpm-lock.yaml and package-lock.json files' full dependencies for dependency installation and updates.

If you need to add dependencies, it is better to use specific versions for the new dependencies, as follows:

 "dependencies": {
    "xlsx": "0.18.5"
 }

If the project has not yet generated lockfile, first generate the corresponding file.

Executing the following command will generate the corresponding pnpm-lock.yaml based on your package.json.

pnpm install --lockfile-only

If the dependencies have already been installed

# 删除 node_modules 和项目内虚拟存储
rm -rf node_modules

# 清空 pnpm 全局 store(内容寻址存储),迫使重新下载
pnpm store prune --force  # 可以先用 pnpm store path 找到路径再手动删除整个 store 目录

# 删除pnpm-lock.yaml
rm -rf pnpm-lock.yaml

pnpm install --lockfile-only

If you are using the pnpm package tool, the recommended operation is to go to the .pnpm-store folder and delete the cached files. This can more completely remove the dependency cache and allow verdaccio to better cache newly added dependencies. The operation is similar for npm.

First, use a fetch request once, which is actually an operation to let verdaccio cache dependencies

# 根据 lockfile 下载所有包到 store(通过 Verdaccio)
pnpm fetch --registry http://localhost:4873

Strengthen a dependency request once, to prevent missing dependency caches, and at the same time download the corresponding dependencies under the project to node_modules

pnpm install --frozen-lockfile --registry http://localhost:4873

Missing Content

Above, we only mentioned the most common scenarios, but there are several other scenarios that cannot be ignored. Due to space constraints, we will just list them here without elaboration and supplement them later.

  • Desktop builds like electron/tauri (involving handling of native binary dependencies)
  • Binary dependencies, how to handle node-gyp, prebuild, etc.
  • Publishing process for private business component libraries is not explained here

Extensions and Thoughts

More mature alternatives include Nexus Repository Manager and Alibaba's cnpm, although cnpmcore has additional requirements in a physically isolated environment. The approach above is intended to remain manageable for frontend developers; teams may choose a more comprehensive repository platform when appropriate.

Explanation of Missing Parts

Example of Electron

Taking the migration of the Electron build environment on Windows as an example:

When building Electron and other desktop applications in a completely physically isolated intranet environment, the core pain point is: Verdaccio can only cache the source code and metadata of npm packages, but Electron and its build tools (like electron-builder) will dynamically download a large number of underlying binaries (such as Electron core, NSIS, winCodeSign, etc.) from GitHub Releases via postinstall scripts during installation or packaging.

To migrate this environment to the intranet, you need to manually take over the caching of these binary files.

Core challenges of offline Electron build migration:

ResourceSourceHandling Method
Electron binary packageGitHub ReleasesManual download/local cache
npm dependencies (JS)npmjs.orgVerdaccio cache
electron-builder toolchainGitHub / various mirrorsManual download / local cache
Native module compilation (node-gyp)Node.js / Electron headersManual download of headers

We don’t need to worry about npm dependencies. What remains are: Electron binary packages, the electron-builder toolchain, and native module compilation (node-gyp).

Solving Electron Binary Packages

When you execute npm install electron, the @electron/get module will download the corresponding electron-vX.Y.Z-[os]-[arch].zip.

Operation on an external network machine: After running npm install electron normally on the external network machine, Electron will cache the downloaded binary package in a specific directory in the local system:

  • Windows: %LOCALAPPDATA%\electron\Cache (usually C:\Users\<username>\AppData\Local\electron\Cache)
  • Linux: ~/.cache/electron
  • macOS: ~/Library/Caches/electron

Migration to an internal network machine:

  1. Locate the corresponding cache directory on the external network machine and package it completely.
  2. Copy it to the internal network machine and extract it to the exact same physical path.
  3. Set the environment variable on the internal network machine to force Electron to skip network downloads and use the cache directly:
    # Windows (cmd)
    set ELECTRON_SKIP_BINARY_DOWNLOAD=1
    Or simply execute npm install normally. As long as the underlying system detects that a Cache compressed package with the matching version hash exists, it will skip the download step.

electron-builder Toolchain Binaries

When you run the packaging command (such as electron-builder --win), it requires that the environment contains packaging toolchain binaries like app-builder, winCodeSign, nsis. These are also not cached in node_modules by default.

External network machine operations: Similar to Electron, electron-builder also has its own independent global cache directory:

  • Windows: %LOCALAPPDATA%\electron-builder\Cache
  • Linux: ~/.cache/electron-builder
  • macOS: ~/Library/Caches/electron-builder

Internal network machine migration:

  1. Fully execute npm run build on an external network machine to ensure that the toolchains for the required target platforms (whether win or linux) are fully downloaded.
  2. Package the corresponding electron-builder\Cache directory from the external network machine.
  3. Copy it to the same path on the internal network machine and unzip it. When the internal machine cannot access the network during packaging, it will automatically fallback to use the cache in this directory.

(Optional advanced method: You can set up a static file server similar to Nginx within the internal network, put the above zip files into it, and then set environment variables ELECTRON_MIRROR and ELECTRON_BUILDER_BINARIES_MIRROR to point to the internal server, but this is a bit more complex than simple copy migration.)

Solving native C++ module issues

For packages like sqlite3 and serialport that depend on underlying C++, the precompiled .node files are usually downloaded through node-pre-gyp or prebuild-install.

Migration Methods:

  1. Based on binary cache: Some prebuilds are cached to %APPDATA%\npm-cache\_prebuilds or %LOCALAPPDATA%\npm-cache, which can be copied together.
  2. Source compilation fallback: If node-gyp must build on site, internal machines need Python and the Visual Studio C++ build toolchain. On the connected machine, also package the Node.js C++ headers—usually under %LOCALAPPDATA%\node-gyp—and copy them into the isolated network.
  3. The most brute-force solution (recommended scenario): Under the condition that the operating system and Node versions of the external and internal machines are exactly the same (e.g., both are Win10 64-bit, Node 18.x), after performing npm install on the external machine, compress the node_modules folder containing the compiled .node files and transfer it to the internal machine. This is the least error-prone way to handle C++ native extensions on an internal network.

Discussion

Support the author
Previous post

Business Front-end System Development Coding Standards Guide and Instructions

Next post

Easy-to-Miss Nginx Details

LMX BlogWeb · TypeScript · Vue · Node.js
Crafted by
@LMXZQQ
Build
unavailable
Date
2026-10-30
Articles
29
Deployed on
Nginx
Source code
GitHub
License
MIT License
Typeface
Geist
Stack
  • next@16.4.3
  • react@19.3.1
  • @base-ui/react@1.8.0
  • shadcn@4.12.0
  • tailwindcss@4.3.3
Analytics
  • OpenPanel
  • Google Analytics
For agents
  • llms.txt
  • index.md
Inspired by
  1. Tailwind CSS
  2. shadcn/ui
  3. Vercel
  4. Apple
  5. Evil Charts
  6. Devouring Details
  7. Skiper UI
  8. Making Software
  9. shadcncraft
Approved by
静水流深 / LMX
1602
Command Palette

Search for a command to run...