Victor is a full stack software engineer who loves travelling and building things. Most recently created Ewolo, a cross-platform workout logger.
Setup a private npm registry

With the recent docker fiasco it has become more and more imperative that we own our own build pipelines and vendor as much of 3rd party code as much as possible. In this article we will look at setting up a production ready private npm registry using verdaccio, an open source npm registry.

Pre-requisites

While verdaccio can run locally, we would like to have something that is accessible over the internet, for example: https://npm-registry.example.org. To that effect we would need host running ubuntu (or your flavor of linux) with root privileges and a webserver installed. This tutorial provides instructions for setting up a reverse proxy to verdaccio via Nginx.

Installation

While verdaccio comes with a docker image, in this setup we will install verdaccio directly in order to reduce the risk of docker itself. The first step then is to install the latest LTS node version via nvm. Once Node.js installed, run npm install --location=global verdaccio. Test that verdaccio is working by simply running it on the commandline: verdaccio. Note that this will create a default config file.

We will now setup the subdomain and test that we can access our registry. With verdaccio running, we use a different session to create /etc/nginx/sites-available/npm-registry as below (change example.org to your domain):

server {
  server_name npm-registry.example.org;

  # add Strict-Transport-Security to prevent man in the middle attacks
  add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload;";
  add_header X-Frame-Options DENY;
  add_header X-Content-Type-Options nosniff;

  access_log /var/log/nginx/npm-registry.example.org.log;

  location / {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-NginX-Proxy true;

    proxy_set_header Host            $host:$server_port;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header X-Forwarded-Proto $scheme;

    proxy_pass http://127.0.0.1:4873;
    proxy_redirect off;

    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
  }
}

Enable the subdomain and setup https via certbot

sudo ln -s /etc/nginx/sites-available/npm-registry /etc/nginx/sites-enabled/npm-registry
sudo nginx -t
sudo service nginx restart
sudo certbot --nginx

If everything was successful then we should be able to head to https://npm-registry.example.org and see the verdaccio installation. In order to test that this works create a .npmrc file in your local node.js project root with the following line in it: registry=https://npm-registry.example.org. This will tell npm to fetch packages from the provided registry. Running npm config get registry should also confirm this. Finally, executing npm install should now also populate the default storage (~/.config/verdaccio/storage) with npm packages on the server. We will now customise the defaults, add authentication and make the service persistent.

Secure the private registry

As it stands the registry is not very private as anyone with the url can start using it as a proxy. We will now customise the defaults to secure our setup.

mkdir ~/verdaccio
mv ~/.config/verdaccio/config.yaml ~/verdaccio/config.yaml
rm ~/.config/verdaccio
mkdir ~/verdaccio/storage
mkdir ~/verdaccio/plugins

Assuming that your home directory is /home/user, we will update the following config settings:

# path to a directory with all packages
storage: /home/user/verdaccio/storage
# path to a directory with plugins to include
plugins: /home/user/verdaccio/plugins

# https://verdaccio.org/docs/configuration#authentication
auth:
  htpasswd:
    file: /home/user/verdaccio/htpasswd
    # Maximum amount of users allowed to register, defaults to "+inf".
    # You can set this to -1 to disable registration.
    max_users: -1
    # Hash algorithm, possible options are: "bcrypt", "md5", "sha1", "crypt".
    algorithm: bcrypt # by default is crypt, but is recommended use bcrypt for new installations
    # Rounds number for "bcrypt", will be ignored for other algorithms.
    rounds: 10

# https://verdaccio.org/docs/configuration#uplinks
# a list of other known repositories we can talk to
uplinks:
  npmjs:
    url: https://registry.npmjs.org/

# Learn how to protect your packages
# https://verdaccio.org/docs/protect-your-dependencies/
# https://verdaccio.org/docs/configuration#packages
packages:
  '@*/*':
    # scoped packages
    access: $authenticated
    publish: $authenticated
    unpublish: $authenticated
    proxy: npmjs

  '**':
    # allow all users (including non-authenticated users) to read and
    # publish all packages
    #
    # you can specify usernames/groupnames (depending on your auth plugin)
    # and three keywords: "$all", "$anonymous", "$authenticated"
    access: $authenticated

    # allow all known users to publish/publish packages
    # (anyone can register by default, remember?)
    publish: $authenticated
    unpublish: $authenticated

    # if package is not available locally, proxy requests to 'npmjs' registry
    proxy: npmjs


# https://verdaccio.org/docs/configuration#security
security:
  api:
    legacy: true
    jwt:
      sign:
        expiresIn: 29d
    web:
      sign:
        expiresIn: 1h # 1 hour by default

Note that by setting max_users to -1 we disallow anyone from register themselves. We also set all access to $authenticated so that no unregistered user can use our private repository. Finally we will create our single registered user via the following:

sudo apt install apache2-utils
htpasswd -c /home/user/verdaccio/htpasswd user
verdaccio --config /home/user/verdaccio/config.yaml

Once verdaccio is running, trying to npm install on the local machine should now result in a code E401. It might be prudent to npm cache clean --force; rm -rf node_modules to force installation from the registry. In order to download packages from our private registry, we will authenticate our machine via: npm login --registry https://npm-registry.example.org --auth-type=legacy. Once authenticated, npm i should work and packages should pop up on the provided storage location on the server as well.

Persistence

In order to persist verdaccio across restarts we will use pm2.

npm i -g pm2
pm2 startup
pm2 start `which verdaccio` -- --config /home/user/verdaccio/config.yaml
pm2 save

Finally, we setup a cron job to flush pm2 log files in order to save space, crontab -e (change the node version to match the pm2 installation):

# 04:0 on Friday - flush all log files
5 4 * * 5 /home/user/.nvm/versions/node/v16.15.0/bin/pm2 flush > /home/user/pm2-flush.log 2>&1

And that is about it, do not forget to add .npmrc to your .gitignore so that you do not leak your registry information :).