How to setup production level private NPM Registry using Verdaccio

Sameer UtradhiSameer Utradhi
11 min read

This article helps you in understanding how verdaccio works and how you can make the most of it. This will help you the most if you also publish your own packages.

Registry

By default if you are downloading packages using npm, you’re using https://registry.npmjs.org/ which is the default registry where the packages that you see on npm are published.

Now here comes verdaccio, it will act as a proxy server for you. Let’s say you want to publish your own packages, but only for your internal company wide use, you can use verdaccio instead of publishing them on npm.

These packages remain only on your server. The server also proxies calls over to npm’s server. If you want to download a public package, you will set registry to your own server and it will keep uplinks to npm’s registry as these packages won’t be available on your private verdaccio registry.

Understanding Verdaccio

A lightweight Node.js private proxy registry

This is how verdaccio describes itself. A private proxy as we learned above.

Verdaccio is a nodejs package in itself. You can create your own registry server just like npm using it and customise it for your own usage.

Installation

npm install verdaccio -g

# yarn
yarn global add verdaccio

Verify your installation using:

verdaccio -v

You are required to install verdaccio globally as it’s expected to be run as standalone server. In case you don’t prefer this method you can also use it through docker.

Starting server

Now all we need to do is run the command:

verdaccio

This will start the server by default on port 4873.

You will see a log similar to this:

verdaccio                                                                                                                          ─╯
 info --- config file  - /Users/sameer/.config/verdaccio/config.yaml
 info --- the "crypt" algorithm is deprecated consider switch to "bcrypt" in the configuration file. Read the documentation for additional details
 info --- using htpasswd file: /Users/sameer/.config/verdaccio/htpasswd
 info --- plugin successfully loaded: verdaccio-htpasswd
 info --- plugin successfully loaded: verdaccio-audit
 warn --- http address - <http://localhost:4873/> - verdaccio/5.27.0

This log is very helpful to understand what all files are used while running a bare minimum verdaccio server.

Architecture

You mainly are required to just edit the config.yaml file. Which from the above example log you can see is loaded once you start the verdaccio server. It is located in a path like: /Users/{username}/.config/verdaccio/config.yaml

Open up the config file and let’s see what all we can do with it!

config.yaml

In this file you define all the configuration setup you want to configure.

# This is the default configuration file. It allows all users to do anything,
# please read carefully the documentation and best practices to
# improve security.
#
# Look here for more config file examples:
# <https://github.com/verdaccio/verdaccio/tree/5.x/conf>
#
# Read about the best practices
# <https://verdaccio.org/docs/best>

# path to a directory with all packages
storage: ./storage
# path to a directory with plugins to include
plugins: ./plugins

# <https://verdaccio.org/docs/webui>
web:
  title: Verdaccio
  # comment out to disable gravatar support
  # gravatar: false
  # by default packages are ordercer ascendant (asc|desc)
  # sort_packages: asc
  # convert your UI to the dark side
  # darkMode: true
  # html_cache: true
  # by default all features are displayed
  # login: true
  # showInfo: true
  # showSettings: true
  # In combination with darkMode you can force specific theme
  # showThemeSwitch: true
  # showFooter: true
  # showSearch: true
  # showRaw: true
  # showDownloadTarball: true
  #  HTML tags injected after manifest <scripts/>
  # scriptsBodyAfter:
  #    - '<script type="text/javascript" src="<https://my.company.com/customJS.min.js>"></script>'
  #  HTML tags injected before ends </head>
  #  metaScripts:
  #    - '<script type="text/javascript" src="<https://code.jquery.com/jquery-3.5.1.slim.min.js>"></script>'
  #    - '<script type="text/javascript" src="<https://browser.sentry-cdn.com/5.15.5/bundle.min.js>"></script>'
  #    - '<meta name="robots" content="noindex" />'
  #  HTML tags injected first child at <body/>
  #  bodyBefore:
  #    - '<div id="myId">html before webpack scripts</div>'
  #  Public path for template manifest scripts (only manifest)
  #  publicPath: <http://somedomain.org/>

# <https://verdaccio.org/docs/configuration#authentication>
auth:
  htpasswd:
    file: ./htpasswd
    # Maximum amount of users allowed to register, defaults to "+inf".
    # You can set this to -1 to disable registration.
    # max_users: 1000
    # 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: $all
    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: $all

    # 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

# To improve your security configuration and  avoid dependency confusion
# consider removing the proxy property for private packages
# <https://verdaccio.org/docs/best#remove-proxy-to-increase-security-at-private-packages>

# <https://verdaccio.org/docs/configuration#server>
# You can specify HTTP/1.1 server keep alive timeout in seconds for incoming connections.
# A value of 0 makes the http server behave similarly to Node.js versions prior to 8.0.0, which did not have a keep-alive timeout.
# WORKAROUND: Through given configuration you can workaround following issue <https://github.com/verdaccio/verdaccio/issues/301>. Set to 0 in case 60 is not enough.
server:
  keepAliveTimeout: 60
  # Allow `req.ip` to resolve properly when Verdaccio is behind a proxy or load-balancer
  # See: <https://expressjs.com/en/guide/behind-proxies.html>
  # trustProxy: '127.0.0.1'

# <https://verdaccio.org/docs/configuration#offline-publish>
# publish:
#   allow_offline: false

# <https://verdaccio.org/docs/configuration#url-prefix>
# url_prefix: /verdaccio/
# VERDACCIO_PUBLIC_URL='<https://somedomain.org>';
# url_prefix: '/my_prefix'
# // url -> <https://somedomain.org/my_prefix/>
# VERDACCIO_PUBLIC_URL='<https://somedomain.org>';
# url_prefix: '/'
# // url -> <https://somedomain.org/>
# VERDACCIO_PUBLIC_URL='<https://somedomain.org/first_prefix>';
# url_prefix: '/second_prefix'
# // url -> <https://somedomain.org/second_prefix/>'

# <https://verdaccio.org/docs/configuration#security>
# security:
#   api:
#     legacy: true
#     jwt:
#       sign:
#         expiresIn: 29d
#       verify:
#         someProp: [value]
#    web:
#      sign:
#        expiresIn: 1h # 1 hour by default
#      verify:
#         someProp: [value]

# <https://verdaccio.org/docs/configuration#user-rate-limit>
# userRateLimit:
#   windowMs: 50000
#   max: 1000

# <https://verdaccio.org/docs/configuration#max-body-size>
# max_body_size: 10mb

# <https://verdaccio.org/docs/configuration#listen-port>
# listen:
# - localhost:4873            # default value
# - <http://localhost:4873>     # same thing
# - 0.0.0.0:4873              # listen on all addresses (INADDR_ANY)
# - <https://example.org:4873>  # if you want to use https
# - "[::1]:4873"                # ipv6
# - unix:/tmp/verdaccio.sock    # unix socket

# The HTTPS configuration is useful if you do not consider use a HTTP Proxy
# <https://verdaccio.org/docs/configuration#https>
# https:
#   key: ./path/verdaccio-key.pem
#   cert: ./path/verdaccio-cert.pem
#   ca: ./path/verdaccio-csr.pem

# <https://verdaccio.org/docs/configuration#proxy>
# http_proxy: <http://something.local/>
# https_proxy: <https://something.local/>

# <https://verdaccio.org/docs/configuration#notifications>
# notify:
#   method: POST
#   headers: [{ "Content-Type": "application/json" }]
#   endpoint: <https://usagge.hipchat.com/v2/room/3729485/notification?auth_token=mySecretToken>
#   content: '{"color":"green","message":"New package published: * {{ name }}*","notify":true,"message_format":"text"}'

middlewares:
  audit:
    enabled: true

# <https://verdaccio.org/docs/logger>
# log settings
log: { type: stdout, format: pretty, level: http }
#experiments:
#  # support for npm token command
#  token: false
#  # disable writing body size to logs, read more on ticket 1912
#  bytesin_off: false
#  # enable tarball URL redirect for hosting tarball with a different server, the tarball_url_redirect can be a template string
#  tarball_url_redirect: '<https://mycdn.com/verdaccio/${packageName}/${filename}>'
#  # the tarball_url_redirect can be a function, takes packageName and filename and returns the url, when working with a js configuration file
#  tarball_url_redirect(packageName, filename) {
#    const signedUrl = // generate a signed url
#    return signedUrl;
#  }

# translate your registry, api i18n not available yet
# i18n:
# list of the available translations <https://github.com/verdaccio/verdaccio/blob/master/packages/plugins/ui-theme/src/i18n/ABOUT_TRANSLATIONS.md>
#   web: en-US

Here I’ve pasted the entire default config file we get with verdaccio version 5.27.0

We will look into the most common configuration items in detail:

storage

This is the folder where all your packages are stored when you are using default configuration, that is storing all your packages on the machine itself.

If you are using a plugin such as verdaccio-aws-s3-storage. Your storage will be in your s3 bucket and this folder will not be used. In this case you can let the config item be as it is, i.e., storage: ./storage as it won’t be used anyway.

Now with the latest versions, it is observed that using it as relative path: ./storage, may cause issues. To resolve it, use absolute path instead. Find where verdaccio is installed globally and pick the storage path from there, ideally the path will be same as config file parent path.

What contents are in storage folder?

Storage folder consists of two main elements:

DB Json file: .verdaccio-db.json

Packages: all the packages are available at the root level of storage folder.

you published packages will contain this two files:

package.json           test-publish-1.0.0.tgz

gzip compressed tar file of your code will be present.

plugins

default path is ./plugins for your custom plugins. If you use third party plugins, the most common ones such as verdaccio-aws-s3-storage provide you an option to configure it via config.yaml itself.

web

Here you can specify configurations for your web interface of the registry. Specifying custom title url’s, name, dark mode and many such configurations can be done from here.

auth

Auth is where you specify login information required to access the registry. The recommended option is to use bcrypt algorithm.

you can configure max number of user login info that the server should store for you.

the htpasswd file looks something like this:

sameer:3p8fkfpAWkCNQ:autocreated 2023-11-17T19:42:30.571Z

password hash used for above is via default crypt algo.

uplinks section is what determines the where the server should make request if the package is not found in the storage folder. Generally you’ll link it to npm registry.

Once you download a specific package, say react, the server will store info regarding it in the storage folder and cache it.

Next time for downloads for any user, it’ll be served from the storage itself. Which will contain uplink of tgz file.

packages

here you specify the info regarding your own private packages and and specify who can publish and unpublish.

For your private packages you can remove the proxy configuration as they will be served from the verdaccio server itself.

server

keepAliveTimeout this is part of http1 spec, you can modify it. Generally it works with default configuration.

VERDACCIO_PUBLIC_URL

This is a very important configuration setup, when you deploy your server to some domain, you need to specify the domain: https://mydomainname.com here.

and also set url_prefix to / by default. If you serve from a nested path then specify that instead.

security

# security:
#   api:
#     legacy: true
#     jwt:
#       sign:
#         expiresIn: 29d
#       verify:
#         someProp: [value]
#    web:
#      sign:
#        expiresIn: 1h # 1 hour by default
#      verify:
#         someProp: [value]

you can uncomment it in the yaml file and configure it. Default configurations work pretty well.

You can also specify notBefore for sign options. An production level config may look like this:

security:
    legacy: true
    api:
        jwt:
            sign:
                expiresIn: 10d
                notBefore: 0
    web:
        sign:
            expiresIn: 10d
            notBefore: 0

rate limiting

npm install is not rate limited. Other calls such as user based calls npm login, npm addUser, … and so are rate limited by default to 100 request peer 15 minutes. And if you browse on the web interface it has rate limit of 5000 requests peer 2 minutes.

you can configure it, but ideally the most important use case will be of installs as the server will be getting used in multiple environments of your build setup. Since it is not rate limited, hardly you’ll have to modify it.

The above information on contents of config.yaml file has covered other parts of verdaccio server architecture that you need to be aware of: auth => htpasswd, storage etc.

Setting up a production grade verdaccio server

Let’s say you start from clean slate on an ec2 machine, you’ll need a decent enough disk storage to as your storage folder can become very big. On safe scale you can start the free tier of 30gb.

Next is installing nodejs on the server, verdaccio needs v12 at least to operate on the latest version.

Then you need to install verdaccio globally. After you’re done with it, you can check by running verdaccio command to check if your server is working as expected.

Then proceed with changes with the config.yaml which is the most important part of the registry setup.

Private packages config

packages:
    '@mycompany/*':
        access: $all
        publish: $authenticated
        unpublish: $authenticated

  '@*/*':
    # scoped packages
    access: $all
    publish: $authenticated
    unpublish: $authenticated
    proxy: npmjs

  '**':
    access: $all
    publish: $authenticated
    unpublish: $authenticated
    proxy: npmjs

Something like above will be your packages configuration which will allow for private packages to be looked on.

Next, very important configuration is setting up public url. If you do not set it, you may run into issues where on your deployed server, the server looks on a local ip to resolve incoming requests.

Publishing your private package will be available on the server which you can view in the web interface.

Issues you can run into

Proxy Issue:

error: tunneling socket could not be established, cause=Hostname/IP does not match certificate's altnames: Host: registry.npmjs.org. is not in the cert's altnames: DNS:*.domain.xyz EACCES: permission denied

This issue may come up if you have defined http_proxy or https_proxy config, once you define the public url, you should be good to go, this proxy will restrict you in resolving packages from uplinks such as npm registry. On hitting yarn install react, the server will throw up the above error as you’ve not whitelisted the npm registry. Either you can remove the proxy config or you can whitelist the domain.

Access Issue:

When installing packages from uplinks or when publishing your own packages, you might run into access issue as you need to give read/write/execute permission to the storage folder. After giving the access the issue will resolve.

Third Party Plugin Issue:

verdaccio-aws-s3-storage is the best example to look here. If you globally install this package and define all the configuration as per there readme, it won’t work.

You’ll need to add this package inside the node_modules of verdaccio package which is installed at the global path.

If you use nvm, you might have to look into the used node versions node_modules and then find verdaccio.

Verdaccio resolved the packages from that path, and once you paste the code there, it’ll start working.

0
Subscribe to my newsletter

Read articles from Sameer Utradhi directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Sameer Utradhi
Sameer Utradhi

Hey there, out of all the smarties in the world you'd to stumble upon my blog. I feel you 🥹