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 :).