Verdaccio
Lightweight private npm proxy registry
Verdaccio is a lightweight private npm registry and proxy that caches packages and hosts private modules. It targets JavaScript teams who need a private package registry. It is deployed via Docker or npm.
Key features
- Private npm registry
- Caching proxy for npmjs
- Pluggable authentication
- Zero-config startup
Pros & cons
Strengths
- Very easy to start
- Lightweight
- Good for private packages
Trade-offs
- npm ecosystem only
- Limited UI
Verdaccio replaces
Last reviewed Aug 26, 2026 · 796 words
Sixty seconds. That is roughly how long it takes to go from nothing to a working private npm registry with npx verdaccio, and it is the whole pitch: Verdaccio is the registry you run when the alternative is a two-hour Nexus install or paying per-seat for a hosted one. It proxies and caches npmjs.org for everything public, hosts your @yourorg/* packages privately, and does both on 256 MB of RAM. The setup is trivial. The three config decisions below are where people get it wrong.
Docker in five lines, then the config file does the work
services:
verdaccio:
image: verdaccio/verdaccio:6
ports:
- "4873:4873"
volumes:
- ./storage:/verdaccio/storage
- ./conf:/verdaccio/conf
restart: unless-stopped
Port 4873 is the default. The volume that matters is storage, which holds every published tarball and every cached upstream package, and conf/config.yaml, which is the entire behaviour of the server. Point your clients at it:
npm set registry http://localhost:4873/
npm adduser --registry http://localhost:4873/
From that moment every npm install goes through Verdaccio. Public packages are fetched from npmjs on first request and served from disk after, so a second npm ci of the same lockfile on the same network no longer touches the internet. On a CI runner that alone is worth the container.
Decision 1: close registration on day one
The default config.yaml allows anyone who can reach the port to npm adduser. That is fine on a laptop and a problem on a server. After creating your accounts, set:
auth:
htpasswd:
file: ./htpasswd
max_users: -1
max_users: -1 disables new signups entirely; existing users in the htpasswd file keep working. For a team of more than a handful, the pluggable auth is the feature: there are plugins for LDAP, GitHub OAuth, and generic OIDC, and it is a single config block to swap. For a homelab, htpasswd and a closed door is plenty.
Decision 2: scope who can publish what
The packages section is an ordered list of glob rules, and the default is looser than you want:
packages:
'@myorg/*':
access: $authenticated
publish: $authenticated
'**':
access: $all
publish: $nobody
proxy: npmjs
Read it top down: your scope is readable and publishable by logged-in users; everything else is readable by anyone, publishable by nobody, and fetched from the npmjs uplink. That last line is what stops a typo'd npm publish from planting a package called lodash in your private registry that shadows the real one for everyone on the network. This is the classic dependency-confusion footgun and the publish: $nobody on the catch-all rule is the fix.
Decision 3: decide what survives a disk failure
Cached public packages are disposable; they come back from npmjs on demand. Private packages are not, and Verdaccio has no built-in backup. The storage directory is plain files, so an rsync or a restic job that includes it is the whole answer, but it has to actually be in the job. The 3-2-1 backups guide covers a rotation that would catch this. If you would rather not think about it, the S3 and Google Cloud storage plugins move the tarballs to a bucket and leave the container stateless.
When you do not need Verdaccio at all
If you already run Gitea or Forgejo, they have a package registry built in that speaks npm (and PyPI, Maven, containers and more), authenticated with the same accounts and permissions you already manage. It does not proxy-cache npmjs, so npm install of public packages still goes out to the internet, but for "publish our 6 private packages somewhere" it is zero extra services. GitLab does the same. Verdaccio wins when you want the cache, when you need per-package publish rules finer than a repo's permissions, or when the registry has to stand alone. It stays firmly in the dev-tools category as a JavaScript-only tool; for container images the equivalent single-purpose registry is Harbor.
The web UI is the real weak spot in the catalogue's list of cons: it lists packages and shows READMEs, and that is about it. Everything operational happens in the YAML file.
What I'd do
Verdaccio 6 in Docker behind Caddy with HTTPS, max_users: -1 after the first adduser, the three-rule packages block above with publish: $nobody on the catch-all, and the storage directory in the nightly backup. Set it as the registry in every CI runner's .npmrc and watch the install times drop. If you are already on Gitea and only need to publish, use its registry instead and skip a service. Either way the decision takes less time than reading this did.
Compare Verdaccio
8 head-to-head comparisons.
Similar developer tools & git apps
Excalidraw
Developer Tools & GitVirtual hand-drawn style whiteboard
Replaces Miro
lazygit
Developer Tools & GitSimple terminal UI for Git commands
Replaces GitKraken, Sourcetree
Hoppscotch
Developer Tools & GitOpen-source API development ecosystem
Replaces Postman, Insomnia
json-server
Developer Tools & GitFull fake REST API from a JSON file in seconds
Replaces Mockoon, Postman Mock
Strapi
Developer Tools & GitLeading open-source headless CMS
Replaces Contentful
NocoDB
Developer Tools & GitOpen-source Airtable alternative
Replaces Airtable